Create http server, Add HTTP mode

- Create http.cpp and http.hpp
  - functions for initializing a http server
  - function for URL api/joystick
    - receive joystick data from http post request
    - parse json, define joystick position (function from joystick.hpp)
    - send data to control task via queue

- control.hpp/cpp:
  - add HTTP mode to handle loop
    - receive joystick commands from queue, generate commands, send to
      motorctl
  - upgrade changeMode function with ability to run functions at switch
    FROM and TO certain modes
    - add code to start/stop wifi and webserver when switching to/from
      HTTP mode
  - change toggleModes and toggleIdle to use the changeMode function

- main.cpp:
  - add several sections with code for testing new functions (commented
    out)
  - add http loglevel

- buzzer.cpp:
  - add command (press 4 times) to toggle between HTTP and JOYSTICK mode

FIXME: moved initialization of wifi to main.cpp at startup because of an
error -> resolve this and place wifi start and stop functions into
mode-change as intended

currently works best in accesspoint mode with laptop connected using the
react-webapp
This commit is contained in:
jonny_ji7 2022-06-17 13:43:43 +02:00 committed by jonny_l480
parent cc5226647b
commit 3cb5bc410b
7 changed files with 397 additions and 30 deletions

View File

@ -1,2 +1,2 @@
idf_component_register(SRCS "main.cpp" "motordrivers.cpp" "motorctl.cpp" "config.cpp" "joystick.cpp" "buzzer.cpp" "control.cpp" "button.cpp" "fan.cpp" "wifi.c"
idf_component_register(SRCS "main.cpp" "motordrivers.cpp" "motorctl.cpp" "config.cpp" "joystick.cpp" "buzzer.cpp" "control.cpp" "button.cpp" "fan.cpp" "wifi.c" "http.cpp"
INCLUDE_DIRS ".")

View File

@ -51,6 +51,11 @@ void buttonCommands::action (uint8_t count){
control->toggleIdle(); //toggle between idle and previous/default mode
break;
case 4:
ESP_LOGW(TAG, "cmd %d: toggle between HTTP and JOYSTICK", count);
control->toggleModes(controlMode_t::HTTP, controlMode_t::JOYSTICK); //toggle between HTTP and JOYSTICK mode
break;
case 6:
ESP_LOGW(TAG, "cmd %d: toggle between MASSAGE and JOYSTICK", count);
control->toggleModes(controlMode_t::MASSAGE, controlMode_t::JOYSTICK); //toggle between MASSAGE and JOYSTICK mode

View File

@ -3,16 +3,22 @@ extern "C"
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "freertos/queue.h"
//custom C libraries
#include "wifi.h"
}
#include "config.hpp"
#include "control.hpp"
#include "http.hpp"
//tag for logging
static const char * TAG = "control";
const char* controlModeStr[6] = {"IDLE", "JOYSTICK", "MASSAGE", "MQTT", "BLUETOOTH", "AUTO"};
const char* controlModeStr[7] = {"IDLE", "JOYSTICK", "MASSAGE", "HTTP", "MQTT", "BLUETOOTH", "AUTO"};
//-----------------------------
//-------- constructor --------
@ -38,18 +44,19 @@ controlledArmchair::controlledArmchair (
//function that repeatedly generates motor commands depending on the current mode
void controlledArmchair::startHandleLoop() {
while (1){
vTaskDelay(20 / portTICK_PERIOD_MS);
ESP_LOGV(TAG, "control task executing... mode=%s", controlModeStr[(int)mode]);
switch(mode) {
default:
mode = controlMode_t::IDLE;
vTaskDelay(200 / portTICK_PERIOD_MS);
break;
case controlMode_t::IDLE:
motorRight->setTarget(motorstate_t::IDLE, 0);
motorLeft->setTarget(motorstate_t::IDLE, 0);
vTaskDelay(200 / portTICK_PERIOD_MS);
break;
case controlMode_t::JOYSTICK:
@ -60,14 +67,36 @@ void controlledArmchair::startHandleLoop() {
motorRight->setTarget(commands.right.state, commands.right.duty);
motorLeft->setTarget(commands.left.state, commands.left.duty);
//TODO make motorctl.setTarget also accept motorcommand struct directly
vTaskDelay(20 / portTICK_PERIOD_MS);
break;
case controlMode_t::MASSAGE:
motorRight->setTarget(motorstate_t::IDLE, 0);
motorLeft->setTarget(motorstate_t::IDLE, 0);
//TODO add actual command generation here
vTaskDelay(20 / portTICK_PERIOD_MS);
break;
case controlMode_t::HTTP:
//create emptry struct for receiving data from http function
joystickData_t dataRead = { };
//get joystick data from queue
if( xQueueReceive( joystickDataQueue, &dataRead, pdMS_TO_TICKS(500) ) ) {
ESP_LOGD(TAG, "received data from http queue: x=%.3f y=%.3f radius=%.3f angle=%.3f",
dataRead.x, dataRead.y, dataRead.radius, dataRead.angle);
//pass received joystick data from http queue to generatecommands function from joystick.hpp
commands = joystick_generateCommandsDriving(dataRead);
ESP_LOGD(TAG, "generated motor commands");
//apply commands to motor control objects
//TODO make motorctl.setTarget also accept motorcommand struct directly
motorRight->setTarget(commands.right.state, commands.right.duty);
motorLeft->setTarget(commands.left.state, commands.left.duty);
}
break;
//TODO: add other modes here
}
}
@ -80,9 +109,65 @@ void controlledArmchair::startHandleLoop() {
//-----------------------------------
//function to change to a specified control mode
void controlledArmchair::changeMode(controlMode_t modeNew) {
ESP_LOGW(TAG, "changing mode from %s to %s", controlModeStr[(int)mode], controlModeStr[(int)modeNew]);
mode = modeNew;
//copy previous mode
controlMode_t modePrevious = mode;
ESP_LOGW(TAG, "=== changing mode from %s to %s ===", controlModeStr[(int)mode], controlModeStr[(int)modeNew]);
//--- run functions when changing FROM certain mode ---
switch(modePrevious){
default:
ESP_LOGI(TAG, "noting to execute when changing FROM this mode");
break;
case controlMode_t::HTTP:
ESP_LOGW(TAG, "switching from http mode -> disabling http and wifi");
//stop http server
ESP_LOGI(TAG, "disabling http server...");
http_stop_server();
//FIXME: make wifi function work here - currently starting wifi at startup (see notes main.cpp)
//stop wifi
//TODO: decide whether ap or client is currently used - which has to be disabled?
//ESP_LOGI(TAG, "deinit wifi...");
//wifi_deinit_client();
//wifi_deinit_ap();
ESP_LOGI(TAG, "done stopping http mode");
break;
}
//--- run functions when changing TO certain mode ---
switch(modeNew){
default:
ESP_LOGI(TAG, "noting to execute when changing TO this mode");
break;
case controlMode_t::HTTP:
ESP_LOGW(TAG, "switching to http mode -> enabling http and wifi");
//start wifi
//TODO: decide wether ap or client should be started
ESP_LOGI(TAG, "init wifi...");
//FIXME: make wifi function work here - currently starting wifi at startup (see notes main.cpp)
//wifi_init_client();
//wifi_init_ap();
//wait for wifi
//ESP_LOGI(TAG, "waiting for wifi...");
//vTaskDelay(1000 / portTICK_PERIOD_MS);
//start http server
ESP_LOGI(TAG, "init http server...");
http_init_server();
ESP_LOGI(TAG, "done initializing http mode");
break;
}
//--- update mode to new mode ---
//TODO: add mutex
mode = modeNew;
}
@ -93,12 +178,12 @@ void controlledArmchair::changeMode(controlMode_t modeNew) {
//function to toggle between IDLE and previous active mode
void controlledArmchair::toggleIdle() {
if (mode == controlMode_t::IDLE){
mode = modePrevious; //restore previous mode, or default if not switched yet
changeMode(modePrevious); //restore previous mode, or default if not switched yet
buzzer->beep(2, 200, 100);
ESP_LOGW(TAG, "switched mode from IDLE to %s", controlModeStr[(int)mode]);
} else {
modePrevious = mode; //store current mode
mode = controlMode_t::IDLE; //set mode to IDLE
changeMode(controlMode_t::IDLE); //set mode to IDLE
buzzer->beep(1, 1000, 0);
ESP_LOGW(TAG, "switched mode from IDLE to %s", controlModeStr[(int)mode]);
}
@ -116,12 +201,12 @@ void controlledArmchair::toggleModes(controlMode_t modePrimary, controlMode_t mo
if (mode == modePrimary){
ESP_LOGW(TAG, "toggleModes: switching from primaryMode %s to secondarMode %s", controlModeStr[(int)mode], controlModeStr[(int)modeSecondary]);
buzzer->beep(2,200,100);
mode = modeSecondary; //switch to secondary mode
changeMode(modeSecondary); //switch to secondary mode
}
//switch to primary mode when any other mode is active
else {
ESP_LOGW(TAG, "toggleModes: switching from %s to primary mode %s", controlModeStr[(int)mode], controlModeStr[(int)modePrimary]);
buzzer->beep(4,200,100);
mode = modePrimary;
changeMode(modePrimary);
}
}

View File

@ -6,9 +6,9 @@
//enum that decides how the motors get controlled
enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, MQTT, BLUETOOTH, AUTO};
enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, HTTP, MQTT, BLUETOOTH, AUTO};
//extern controlMode_t mode;
extern const char* controlModeStr[6];
extern const char* controlModeStr[7];

221
main/http.cpp Normal file
View File

@ -0,0 +1,221 @@
extern "C"
{
#include <stdio.h>
#include "mdns.h"
#include "cJSON.h"
#include "esp_http_server.h"
#include "esp_spiffs.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "freertos/queue.h"
}
#include "http.hpp"
#include "joystick.hpp"
//tag for logging
static const char * TAG = "http";
static httpd_handle_t server = NULL;
QueueHandle_t joystickDataQueue = xQueueCreate( 20, sizeof( struct joystickData_t ) );
//joystickData_t http_readFromJoystickQueue
//==============================
//===== start mdns service =====
//==============================
//function that initializes and starts mdns server for host discovery
void start_mdns_service()
{
//init queue for sending joystickdata from http endpoint to control task
mdns_init();
mdns_hostname_set("armchair");
mdns_instance_name_set("electric armchair");
}
//===========================
//======= default url =======
//===========================
//serve requested files from spiffs
static esp_err_t on_default_url(httpd_req_t *req)
{
ESP_LOGI(TAG, "Opening page for URL: %s", req->uri);
esp_vfs_spiffs_conf_t esp_vfs_spiffs_conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = true};
esp_vfs_spiffs_register(&esp_vfs_spiffs_conf);
char path[600];
if (strcmp(req->uri, "/") == 0)
strcpy(path, "/spiffs/index.html");
else
sprintf(path, "/spiffs%s", req->uri);
char *ext = strrchr(path, '.');
if (ext == NULL || strncmp(ext, ".local", strlen(".local")) == 0)
{
httpd_resp_set_status(req, "301 Moved Permanently");
httpd_resp_set_hdr(req, "Location", "/");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
if (strcmp(ext, ".css") == 0)
httpd_resp_set_type(req, "text/css");
if (strcmp(ext, ".js") == 0)
httpd_resp_set_type(req, "text/javascript");
if (strcmp(ext, ".png") == 0)
httpd_resp_set_type(req, "image/png");
FILE *file = fopen(path, "r");
if (file == NULL)
{
httpd_resp_send_404(req);
esp_vfs_spiffs_unregister(NULL);
return ESP_OK;
}
char lineRead[256];
while (fgets(lineRead, sizeof(lineRead), file))
{
httpd_resp_sendstr_chunk(req, lineRead);
}
httpd_resp_sendstr_chunk(req, NULL);
esp_vfs_spiffs_unregister(NULL);
return ESP_OK;
}
//===============================
//====== joystick endpoint ======
//===============================
//function that is called when data is received with post request at /api/joystick
esp_err_t on_joystick_url(httpd_req_t *req)
{
//--- add header ---
//to allow cross origin (otherwise browser fails when app is running on another host)
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
//--- get data from http request ---
char buffer[100];
memset(&buffer, 0, sizeof(buffer));
httpd_req_recv(req, buffer, req->content_len);
ESP_LOGD(TAG, "/api/joystick: received data: %s", buffer);
//--- parse received json string to json object ---
cJSON *payload = cJSON_Parse(buffer);
ESP_LOGV(TAG, "parsed json: \n %s", cJSON_Print(payload));
//--- extract relevant items from json object ---
cJSON *x_json = cJSON_GetObjectItem(payload, "x");
cJSON *y_json = cJSON_GetObjectItem(payload, "y");
cJSON *radius_json = cJSON_GetObjectItem(payload, "radius");
cJSON *angle_json = cJSON_GetObjectItem(payload, "angle");
//--- save items to struct ---
joystickData_t data = { };
//note cjson can only interpret values as numbers when there are no quotes around the values in json (are removed from json on client side)
//convert json to double to float
data.x = static_cast<float>(x_json->valuedouble);
data.y = static_cast<float>(y_json->valuedouble);
data.radius = static_cast<float>(radius_json->valuedouble);
data.angle = static_cast<float>(angle_json->valuedouble);
//--- evaluate joystick position enum ---
data.position = joystick_evaluatePosition(data.x, data.y);
//log received and parsed values
ESP_LOGI(TAG, "parsed values from received json: \n x=%.3f y=%.3f radius=%.3f angle=%.3f",
data.x, data.y, data.radius, data.angle);
//--- free memory ---
cJSON_Delete(payload);
//--- send data to control task via queue ---
xQueueSend( joystickDataQueue, ( void * )&data, ( TickType_t ) 0 );
//--- return http response ---
httpd_resp_set_status(req, "204 NO CONTENT");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
//============================
//===== init http server =====
//============================
//function that initializes http server and configures available urls
void http_init_server()
{
//configure webserver
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.uri_match_fn = httpd_uri_match_wildcard;
//start webserver
ESP_ERROR_CHECK(httpd_start(&server, &config));
//---------------------------
//------- define URLs -------
//---------------------------
httpd_uri_t joystick_url = {
.uri = "/api/joystick",
.method = HTTP_POST,
.handler = on_joystick_url,
};
httpd_register_uri_handler(server, &joystick_url);
// httpd_uri_t default_url = {
// .uri = "/*",
// .method = HTTP_GET,
// .handler = on_default_url};
// httpd_register_uri_handler(server, &default_url);
// httpd_uri_t socket_joystick_url = {
// .uri = "/ws-api/joystick",
// .method = HTTP_GET,
// .handler = on_socket_joystick_url,
// .is_websocket = true};
// httpd_register_uri_handler(server, &socket_joystick_url);
}
//============================
//===== stop http server =====
//============================
//function that destroys the http server
void http_stop_server()
{
printf("stopping http\n");
httpd_stop(server);
}

23
main/http.hpp Normal file
View File

@ -0,0 +1,23 @@
#pragma once
extern QueueHandle_t joystickDataQueue;
//============================
//===== init http server =====
//============================
//function that initializes http server and configures available urls
void http_init_server();
//==============================
//===== start mdns service =====
//==============================
//function that initializes and starts mdns server for host discovery
void start_mdns_service();
//============================
//===== stop http server =====
//============================
//function that destroys the http server
void http_stop_server();

View File

@ -12,12 +12,15 @@ extern "C"
#include "driver/ledc.h"
//custom C files
#include "wifi.h"
}
//custom C++ files
#include "config.hpp"
#include "control.hpp"
#include "button.hpp"
#include "http.hpp"
//tag for logging
static const char * TAG = "main";
@ -106,25 +109,25 @@ extern "C" void app_main(void) {
gpio_set_level(GPIO_NUM_17, 1);
//initialize nvs-flash and netif (needed for wifi)
wifi_initNvs_initNetif();
//-------------------------------
//---------- log level ----------
//-------------------------------
//set loglevel for all tags:
esp_log_level_set("*", ESP_LOG_WARN);
//set loglevel for individual tags:
//--- set loglevel for individual tags ---
esp_log_level_set("main", ESP_LOG_INFO);
esp_log_level_set("buzzer", ESP_LOG_ERROR);
//esp_log_level_set("motordriver", ESP_LOG_DEBUG);
//esp_log_level_set("motor-control", ESP_LOG_DEBUG);
esp_log_level_set("motor-control", ESP_LOG_INFO);
//esp_log_level_set("evaluatedJoystick", ESP_LOG_DEBUG);
//esp_log_level_set("joystickCommands", ESP_LOG_DEBUG);
esp_log_level_set("button", ESP_LOG_INFO);
esp_log_level_set("control", ESP_LOG_INFO);
esp_log_level_set("control", ESP_LOG_DEBUG);
esp_log_level_set("fan-control", ESP_LOG_INFO);
esp_log_level_set("wifi", ESP_LOG_INFO);
esp_log_level_set("http", ESP_LOG_INFO);
//----------------------------------------------
@ -161,25 +164,55 @@ extern "C" void app_main(void) {
buzzer.beep(3, 70, 50);
//--- initialize nvs-flash and netif (needed for wifi) ---
wifi_initNvs_initNetif();
//--- initialize and start wifi ---
//FIXME: run wifi_init_client or wifi_init_ap as intended from control.cpp when switching state
//currently commented out because of error "assert failed: xQueueSemaphoreTake queue.c:1549 (pxQueue->uxItemSize == 0)" when calling control->changeMode from button.cpp
//when calling control.changeMode(http) from main.cpp it worked without error for some reason?
ESP_LOGI(TAG,"starting wifi...");
//wifi_init_client(); //connect to existing wifi
wifi_init_ap(); //start access point
ESP_LOGI(TAG,"done starting wifi");
//--- testing http server ---
// wifi_init_client(); //connect to existing wifi
// vTaskDelay(2000 / portTICK_PERIOD_MS);
// ESP_LOGI(TAG, "initializing http server");
// http_init_server();
//--- testing force http mode after startup ---
//control.changeMode(controlMode_t::HTTP);
while(1){
vTaskDelay(500 / portTICK_PERIOD_MS);
// //--- testing functions at mode change HTTP ---
// control.changeMode(controlMode_t::HTTP);
// vTaskDelay(10000 / portTICK_PERIOD_MS);
// control.changeMode(controlMode_t::IDLE);
// vTaskDelay(10000 / portTICK_PERIOD_MS);
vTaskDelay(500 / portTICK_PERIOD_MS);
//--- testing wifi functions ---
ESP_LOGI(TAG, "creating AP");
wifi_init_ap(); //start accesspoint
vTaskDelay(15000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "stopping wifi");
wifi_deinit_ap(); //stop wifi access point
vTaskDelay(5000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "connecting to wifi");
wifi_init_client(); //connect to existing wifi
vTaskDelay(10000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "stopping wifi");
wifi_deinit_client(); //stop wifi client
vTaskDelay(5000 / portTICK_PERIOD_MS);
// ESP_LOGI(TAG, "creating AP");
// wifi_init_ap(); //start accesspoint
// vTaskDelay(15000 / portTICK_PERIOD_MS);
// ESP_LOGI(TAG, "stopping wifi");
// wifi_deinit_ap(); //stop wifi access point
// vTaskDelay(5000 / portTICK_PERIOD_MS);
// ESP_LOGI(TAG, "connecting to wifi");
// wifi_init_client(); //connect to existing wifi
// vTaskDelay(10000 / portTICK_PERIOD_MS);
// ESP_LOGI(TAG, "stopping wifi");
// wifi_deinit_client(); //stop wifi client
// vTaskDelay(5000 / portTICK_PERIOD_MS);