Merge branch 'origin/dev'

This commit is contained in:
jonny_ji7 2022-06-25 13:00:08 +02:00
commit fd90ca0bb2
11 changed files with 415 additions and 156 deletions

View File

@ -27,7 +27,7 @@ single100a_config_t configDriverRight = {
.pwmFreq = 10000 .pwmFreq = 10000
}; };
//configure motor contol //--- configure motor contol ---
motorctl_config_t configMotorControl = { motorctl_config_t configMotorControl = {
.msFade = 900, .msFade = 900,
.currentMax = 10 .currentMax = 10
@ -39,6 +39,31 @@ controlledMotor motorRight(configDriverRight, configMotorControl);
//------------------------------
//------- control config -------
//------------------------------
control_config_t configControl = {
.defaultMode = controlMode_t::JOYSTICK, //default mode after startup and toggling IDLE
//--- timeout ---
.timeoutMs = 5*60*1000, //time of inactivity after which the mode gets switched to IDLE
.timeoutTolerancePer = 5, //percentage the duty can vary between timeout checks considered still inactive
//--- http mode ---
};
//-------------------------------
//----- httpJoystick config -----
//-------------------------------
httpJoystick_config_t configHttpJoystickMain{
.toleranceZeroX_Per = 3, //percentage around joystick axis the coordinate snaps to 0
.toleranceZeroY_Per = 10,
.toleranceEndPer = 2, //percentage before joystick end the coordinate snaps to 1/-1
.timeoutMs = 3000 //time no new data was received before the motors get turned off
};
//-------------------------------------- //--------------------------------------
//------- joystick configuration ------- //------- joystick configuration -------
@ -46,10 +71,11 @@ controlledMotor motorRight(configDriverRight, configMotorControl);
joystick_config_t configJoystick = { joystick_config_t configJoystick = {
.adc_x = ADC1_CHANNEL_3, //GPIO39 .adc_x = ADC1_CHANNEL_3, //GPIO39
.adc_y = ADC1_CHANNEL_0, //GPIO36 .adc_y = ADC1_CHANNEL_0, //GPIO36
//range around center-threshold of each axis the coordinates stays at 0 (adc value 0-4095) //percentage of joystick range the coordinate of the axis snaps to 0 (0-100)
.tolerance_zero = 100, .tolerance_zeroX_per = 7,
//threshold the coordinate snaps to -1 or 1 before configured "_max" or "_min" threshold (mechanical end) is reached (adc value 0-4095) .tolerance_zeroY_per = 3,
.tolerance_end = 80, //percentage of joystick range the coordinate snaps to -1 or 1 before configured "_max" or "_min" threshold (mechanical end) is reached (0-100)
.tolerance_end_per = 5,
//threshold the radius jumps to 1 before the stick is at max radius (range 0-1) //threshold the radius jumps to 1 before the stick is at max radius (range 0-1)
.tolerance_radius = 0.05, .tolerance_radius = 0.05,
@ -63,20 +89,11 @@ joystick_config_t configJoystick = {
.y_inverted = true .y_inverted = true
}; };
//create global joystic instance
evaluatedJoystick joystick(configJoystick);
//create global evaluated switch instance for button next to joystick
gpio_evaluatedSwitch buttonJoystick(GPIO_NUM_33, true, false); //pullup true, not inverted (switch to GND use pullup of controller)
//create buzzer object on pin 12 with gap between queued events of 100ms
buzzer_t buzzer(GPIO_NUM_12, 100);
//create global control object
controlledArmchair control(&buzzer, &motorLeft, &motorRight);
//configure fan contol //----------------------------
//--- configure fan contol ---
//----------------------------
fan_config_t configFanLeft = { fan_config_t configFanLeft = {
.gpio_fan = GPIO_NUM_2, .gpio_fan = GPIO_NUM_2,
.msRun = 5000, .msRun = 5000,
@ -88,3 +105,25 @@ fan_config_t configFanRight = {
.dutyThreshold = 35 .dutyThreshold = 35
}; };
//=================================
//===== create global objects =====
//=================================
//create global joystic instance
evaluatedJoystick joystick(configJoystick);
//create global evaluated switch instance for button next to joystick
gpio_evaluatedSwitch buttonJoystick(GPIO_NUM_33, true, false); //pullup true, not inverted (switch to GND use pullup of controller)
//create buzzer object on pin 12 with gap between queued events of 100ms
buzzer_t buzzer(GPIO_NUM_12, 100);
//create global httpJoystick object
httpJoystick httpJoystickMain(configHttpJoystickMain);
//create global control object
controlledArmchair control(configControl, &buzzer, &motorLeft, &motorRight, &httpJoystickMain);

View File

@ -8,6 +8,7 @@
#include "buzzer.hpp" #include "buzzer.hpp"
#include "control.hpp" #include "control.hpp"
#include "fan.hpp" #include "fan.hpp"
#include "http.hpp"
//create global controlledMotor instances for both motors //create global controlledMotor instances for both motors
@ -26,6 +27,9 @@ extern buzzer_t buzzer;
//create global control object //create global control object
extern controlledArmchair control; extern controlledArmchair control;
//create global httpJoystick object
extern httpJoystick httpJoystickMain;
//configuration for fans //configuration for fans
extern fan_config_t configFanLeft; extern fan_config_t configFanLeft;
extern fan_config_t configFanRight; extern fan_config_t configFanRight;

View File

@ -12,7 +12,6 @@ extern "C"
#include "config.hpp" #include "config.hpp"
#include "control.hpp" #include "control.hpp"
#include "http.hpp"
//tag for logging //tag for logging
@ -24,15 +23,23 @@ const char* controlModeStr[7] = {"IDLE", "JOYSTICK", "MASSAGE", "HTTP", "MQTT",
//-------- constructor -------- //-------- constructor --------
//----------------------------- //-----------------------------
controlledArmchair::controlledArmchair ( controlledArmchair::controlledArmchair (
control_config_t config_f,
buzzer_t * buzzer_f, buzzer_t * buzzer_f,
controlledMotor* motorLeft_f, controlledMotor* motorLeft_f,
controlledMotor* motorRight_f controlledMotor* motorRight_f,
httpJoystick* httpJoystick_f
){ ){
//copy configuration
config = config_f;
//copy object pointers //copy object pointers
buzzer = buzzer_f; buzzer = buzzer_f;
motorLeft = motorLeft_f; motorLeft = motorLeft_f;
motorRight = motorRight_f; motorRight = motorRight_f;
httpJoystickMain_l = httpJoystick_f;
//set default mode from config
modePrevious = config.defaultMode;
//TODO declare / configure controlled motors here instead of config (unnecessary that button object is globally available - only used here)? //TODO declare / configure controlled motors here instead of config (unnecessary that button object is globally available - only used here)?
} }
@ -83,38 +90,92 @@ void controlledArmchair::startHandleLoop() {
joystickData_t dataRead = { }; joystickData_t dataRead = { };
//--- get joystick data from queue --- //--- get joystick data from queue ---
if( xQueueReceive( joystickDataQueue, &dataRead, pdMS_TO_TICKS(500) ) ) { //Note this function waits several seconds (httpconfig.timeoutMs) for data to arrive, otherwise Center data or NULL is returned
//reset timestamp lastAction //TODO: as described above, when changing modes it might delay a few seconds for the change to apply
http_timestamp_lastData = esp_log_timestamp(); dataRead = httpJoystickMain_l->getData();
//--- generate motor commands ---
ESP_LOGD(TAG, "generating commands from x=%.3f y=%.3f radius=%.3f angle=%.3f", dataRead.x, dataRead.y, dataRead.radius, dataRead.angle);
//Note: timeout (no data received) is handled in getData method
commands = joystick_generateCommandsDriving(dataRead);
ESP_LOGD(TAG, "received data from http queue -> generating commands\n x=%.3f y=%.3f radius=%.3f angle=%.3f", //--- apply commands to motors ---
dataRead.x, dataRead.y, dataRead.radius, dataRead.angle); //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;
//--- generate motor commands --- // //TODO: add other modes here
//pass received joystick data from http queue to generatecommands function from joystick.hpp }
commands = joystick_generateCommandsDriving(dataRead);
//--- apply commands to motors ---
//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);
}
//--- timeout --- //-----------------------
//turn off motors when motor still on and no new data received for some time //------ slow loop ------
if ( //-----------------------
(esp_log_timestamp() - http_timestamp_lastData > 3000) //no data received for x seconds //this section is run about every 5s (+500ms)
&& (commands.left.state != motorstate_t::IDLE || commands.right.state != motorstate_t::IDLE) //at least one motor is still running if (esp_log_timestamp() - timestamp_SlowLoopLastRun > 5000) {
){ ESP_LOGV(TAG, "running slow loop... time since last run: %.1fs", (float)(esp_log_timestamp() - timestamp_SlowLoopLastRun)/1000);
ESP_LOGE(TAG, "TIMEOUT - no data received for 3s -> stopping motors"); timestamp_SlowLoopLastRun = esp_log_timestamp();
//copy preset commands for idling both motors
commands = cmds_bothMotorsIdle;
motorRight->setTarget(commands.right.state, commands.right.duty);
motorLeft->setTarget(commands.left.state, commands.left.duty);
}
break; //run function which detects timeout (switch to idle)
//TODO: add other modes here handleTimeout();
}
}//end while(1)
}//end startHandleLoop
//-----------------------------------
//---------- resetTimeout -----------
//-----------------------------------
void controlledArmchair::resetTimeout(){
//TODO mutex
timestamp_lastActivity = esp_log_timestamp();
}
//------------------------------------
//---------- handleTimeout -----------
//------------------------------------
float inactivityTolerance = 10; //percentage the duty can vary since last timeout check and still counts as incative
//local function that checks whether two values differ more than a given tolerance
bool validateActivity(float dutyOld, float dutyNow, float tolerance){
float dutyDelta = dutyNow - dutyOld;
if (fabs(dutyDelta) < tolerance) {
return false; //no significant activity detected
} else {
return true; //there was activity
}
}
//function that evaluates whether there is no activity/change on the motor duty for a certain time. If so, a switch to IDLE is issued. - has to be run repeatedly in a slow interval
void controlledArmchair::handleTimeout(){
//check for timeout only when not idling already
if (mode != controlMode_t::IDLE) {
//get current duty from controlled motor objects
float dutyLeftNow = motorLeft->getStatus().duty;
float dutyRightNow = motorRight->getStatus().duty;
//activity detected on any of the two motors
if (validateActivity(dutyLeft_lastActivity, dutyLeftNow, inactivityTolerance)
|| validateActivity(dutyRight_lastActivity, dutyRightNow, inactivityTolerance)
){
ESP_LOGD(TAG, "timeout check: detected [activity] since last check -> reset");
//reset last duty and timestamp
timestamp_lastActivity = esp_log_timestamp();
dutyLeft_lastActivity = dutyLeftNow;
dutyRight_lastActivity = dutyRightNow;
}
//no activity on any motor and msTimeout exceeded
else if (esp_log_timestamp() - timestamp_lastActivity > config.timeoutMs){
ESP_LOGI(TAG, "timeout check: [TIMEOUT], no activity for more than %.ds -> switch to idle", config.timeoutMs/1000);
//toggle to idle mode
toggleIdle();
}
else {
ESP_LOGD(TAG, "timeout check: [inactive], last activity %.1f seconds ago", (float)(esp_log_timestamp() - timestamp_lastActivity)/1000);
} }
} }
} }
@ -126,6 +187,9 @@ void controlledArmchair::startHandleLoop() {
//----------------------------------- //-----------------------------------
//function to change to a specified control mode //function to change to a specified control mode
void controlledArmchair::changeMode(controlMode_t modeNew) { void controlledArmchair::changeMode(controlMode_t modeNew) {
//reset timeout timer
resetTimeout();
//copy previous mode //copy previous mode
controlMode_t modePrevious = mode; controlMode_t modePrevious = mode;
@ -161,6 +225,10 @@ void controlledArmchair::changeMode(controlMode_t modeNew) {
ESP_LOGI(TAG, "noting to execute when changing TO this mode"); ESP_LOGI(TAG, "noting to execute when changing TO this mode");
break; break;
case controlMode_t::IDLE:
buzzer->beep(1, 1500, 0);
break;
case controlMode_t::HTTP: case controlMode_t::HTTP:
ESP_LOGW(TAG, "switching to http mode -> enabling http and wifi"); ESP_LOGW(TAG, "switching to http mode -> enabling http and wifi");
//start wifi //start wifi
@ -197,12 +265,11 @@ void controlledArmchair::toggleIdle() {
if (mode == controlMode_t::IDLE){ if (mode == controlMode_t::IDLE){
changeMode(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); buzzer->beep(2, 200, 100);
ESP_LOGW(TAG, "switched mode from IDLE to %s", controlModeStr[(int)mode]); ESP_LOGW(TAG, "toggle idle: switched mode from IDLE to %s", controlModeStr[(int)mode]);
} else { } else {
modePrevious = mode; //store current mode modePrevious = mode; //store current mode
changeMode(controlMode_t::IDLE); //set mode to IDLE changeMode(controlMode_t::IDLE); //set mode to IDLE
buzzer->beep(1, 1000, 0); ESP_LOGW(TAG, "toggle idle: switched mode from %s to IDLE", controlModeStr[(int)mode]);
ESP_LOGW(TAG, "switched mode from IDLE to %s", controlModeStr[(int)mode]);
} }
} }

View File

@ -3,13 +3,26 @@
#include "motordrivers.hpp" #include "motordrivers.hpp"
#include "motorctl.hpp" #include "motorctl.hpp"
#include "buzzer.hpp" #include "buzzer.hpp"
#include "http.hpp"
//--------------------------------------------
//---- struct, enum, variable declarations ---
//--------------------------------------------
//enum that decides how the motors get controlled //enum that decides how the motors get controlled
enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, HTTP, MQTT, BLUETOOTH, AUTO}; enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, HTTP, MQTT, BLUETOOTH, AUTO};
//extern controlMode_t mode; //string array representing the mode enum (for printing the state as string)
extern const char* controlModeStr[7]; extern const char* controlModeStr[7];
//struct with config parameters
typedef struct control_config_t {
controlMode_t defaultMode; //default mode after startup and toggling IDLE
//--- timeout ---
uint32_t timeoutMs; //time of inactivity after which the mode gets switched to IDLE
float timeoutTolerancePer; //percentage the duty can vary between timeout checks considered still inactive
} control_config_t;
//================================== //==================================
@ -21,9 +34,11 @@ class controlledArmchair {
public: public:
//--- constructor --- //--- constructor ---
controlledArmchair ( controlledArmchair (
control_config_t config_f,
buzzer_t* buzzer_f, buzzer_t* buzzer_f,
controlledMotor* motorLeft_f, controlledMotor* motorLeft_f,
controlledMotor* motorRight_f controlledMotor* motorRight_f,
httpJoystick* httpJoystick_f
); );
//--- functions --- //--- functions ---
@ -39,16 +54,26 @@ class controlledArmchair {
//function that toggles between two modes, but prefers first argument if entirely different mode is currently active //function that toggles between two modes, but prefers first argument if entirely different mode is currently active
void toggleModes(controlMode_t modePrimary, controlMode_t modeSecondary); void toggleModes(controlMode_t modePrimary, controlMode_t modeSecondary);
//function that restarts timer which initiates the automatic timeout (switch to IDLE) after certain time of inactivity
void resetTimeout();
private: private:
//--- functions ---
//function that evaluates whether there is no activity/change on the motor duty for a certain time, if so a switch to IDLE is issued. - has to be run repeatedly in a slow interval
void handleTimeout();
//--- objects --- //--- objects ---
buzzer_t* buzzer; buzzer_t* buzzer;
controlledMotor* motorLeft; controlledMotor* motorLeft;
controlledMotor* motorRight; controlledMotor* motorRight;
httpJoystick* httpJoystickMain_l;
//---variables --- //---variables ---
//struct for motor commands returned by generate functions of each mode //struct for motor commands returned by generate functions of each mode
motorCommands_t commands; motorCommands_t commands;
//struct with config parameters
control_config_t config;
//variables for http mode //variables for http mode
uint32_t http_timestamp_lastData = 0; uint32_t http_timestamp_lastData = 0;
@ -57,7 +82,7 @@ class controlledArmchair {
controlMode_t mode = controlMode_t::IDLE; controlMode_t mode = controlMode_t::IDLE;
//variable to store mode when toggling IDLE mode //variable to store mode when toggling IDLE mode
controlMode_t modePrevious = controlMode_t::JOYSTICK; //default mode controlMode_t modePrevious; //default mode
//command preset for idling motors //command preset for idling motors
const motorCommand_t cmd_motorIdle = { const motorCommand_t cmd_motorIdle = {
@ -68,6 +93,14 @@ class controlledArmchair {
.left = cmd_motorIdle, .left = cmd_motorIdle,
.right = cmd_motorIdle .right = cmd_motorIdle
}; };
//variable for slow loop
uint32_t timestamp_SlowLoopLastRun = 0;
//variables for detecting timeout (switch to idle, after inactivity)
float dutyLeft_lastActivity = 0;
float dutyRight_lastActivity = 0;
uint32_t timestamp_lastActivity = 0;
}; };

View File

@ -3,7 +3,6 @@ extern "C"
#include <stdio.h> #include <stdio.h>
#include "mdns.h" #include "mdns.h"
#include "cJSON.h" #include "cJSON.h"
#include "esp_http_server.h"
#include "esp_spiffs.h" #include "esp_spiffs.h"
#include "esp_wifi.h" #include "esp_wifi.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
@ -14,14 +13,13 @@ extern "C"
} }
#include "http.hpp" #include "http.hpp"
#include "joystick.hpp" #include "config.hpp"
//tag for logging //tag for logging
static const char * TAG = "http"; static const char * TAG = "http";
static httpd_handle_t server = NULL; static httpd_handle_t server = NULL;
QueueHandle_t joystickDataQueue = xQueueCreate( 1, sizeof( struct joystickData_t ) );
//joystickData_t http_readFromJoystickQueue //joystickData_t http_readFromJoystickQueue
@ -99,12 +97,25 @@ static esp_err_t on_default_url(httpd_req_t *req)
//=============================== //==============================
//====== joystick endpoint ====== //===== httpJoystick class =====
//=============================== //==============================
//function that is called when data is received with post request at /api/joystick //-----------------------
esp_err_t on_joystick_url(httpd_req_t *req) //----- constructor -----
{ //-----------------------
httpJoystick::httpJoystick( httpJoystick_config_t config_f ){
//copy config struct
config = config_f;
//initialize queue for joystick data
QueueHandle_t joystickDataQueue = xQueueCreate( 1, sizeof( struct joystickData_t ) );
}
//--------------------------
//---- receiveHttpData -----
//--------------------------
//joystick endpoint - function that is called when data is received with post request at /api/joystick
esp_err_t httpJoystick::receiveHttpData(httpd_req_t *req){
//--- add header --- //--- add header ---
//to allow cross origin (otherwise browser fails when app is running on another host) //to allow cross origin (otherwise browser fails when app is running on another host)
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
@ -122,8 +133,6 @@ esp_err_t on_joystick_url(httpd_req_t *req)
//--- extract relevant items from json object --- //--- extract relevant items from json object ---
cJSON *x_json = cJSON_GetObjectItem(payload, "x"); cJSON *x_json = cJSON_GetObjectItem(payload, "x");
cJSON *y_json = cJSON_GetObjectItem(payload, "y"); 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 --- //--- save items to struct ---
joystickData_t data = { }; joystickData_t data = { };
@ -132,34 +141,87 @@ esp_err_t on_joystick_url(httpd_req_t *req)
//convert json to double to float //convert json to double to float
data.x = static_cast<float>(x_json->valuedouble); data.x = static_cast<float>(x_json->valuedouble);
data.y = static_cast<float>(y_json->valuedouble); data.y = static_cast<float>(y_json->valuedouble);
data.radius = static_cast<float>(radius_json->valuedouble); //log received and parsed values
data.angle = static_cast<float>(angle_json->valuedouble); ESP_LOGI(TAG, "received values: x=%.3f y=%.3f",
data.x, data.y);
//--- evaluate joystick position enum --- // scaleCoordinate(input, min, max, center, tolerance_zero_per, tolerance_end_per)
data.x = scaleCoordinate(data.x+1, 0, 2, 1, config.toleranceZeroX_Per, config.toleranceEndPer);
data.y = scaleCoordinate(data.y+1, 0, 2, 1, config.toleranceZeroY_Per, config.toleranceEndPer);
//--- calculate radius with new/scaled coordinates ---
data.radius = sqrt(pow(data.x,2) + pow(data.y,2));
//TODO: radius tolerance? (as in original joystick func)
//limit radius to 1
if (data.radius > 1) {
data.radius = 1;
}
//--- calculate angle ---
data.angle = (atan(data.y/data.x) * 180) / 3.141;
//--- evaluate position ---
data.position = joystick_evaluatePosition(data.x, data.y); data.position = joystick_evaluatePosition(data.x, data.y);
//log processed values
//log received and parsed values ESP_LOGI(TAG, "processed values: x=%.3f y=%.3f radius=%.3f angle=%.3f pos=%s",
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, joystickPosStr[(int)data.position]);
data.x, data.y, data.radius, data.angle);
//--- free memory --- //--- free memory ---
cJSON_Delete(payload); cJSON_Delete(payload);
//--- send data to control task via queue --- //--- send data to control task via queue ---
//xQueueSend( joystickDataQueue, ( void * )&data, ( TickType_t ) 0 ); //xQueueSend( joystickDataQueue, ( void * )&data, ( TickType_t ) 0 );
//changed to length = 1 -> overwrite - older values are no longer relevant //changed to length = 1 -> overwrite - older values are no longer relevant
xQueueOverwrite( joystickDataQueue, ( void * )&data ); xQueueOverwrite( joystickDataQueue, ( void * )&data );
//--- return http response --- //--- return http response ---
httpd_resp_set_status(req, "204 NO CONTENT"); httpd_resp_set_status(req, "204 NO CONTENT");
httpd_resp_send(req, NULL, 0); httpd_resp_send(req, NULL, 0);
return ESP_OK; return ESP_OK;
} }
//-------------------
//----- getData -----
//-------------------
//wait for and return joystick data from queue, if timeout return NULL
joystickData_t httpJoystick::getData(){
//--- get joystick data from queue ---
if( xQueueReceive( joystickDataQueue, &dataRead, pdMS_TO_TICKS(config.timeoutMs) ) ) {
ESP_LOGD(TAG, "getData: received data (from queue): x=%.3f y=%.3f radius=%.3f angle=%.3f",
dataRead.x, dataRead.y, dataRead.radius, dataRead.angle);
}
//--- timeout ---
//no new data received within configured timeout
else {
//send error message when last received data did NOT result in CENTER position
if (dataRead.position != joystickPos_t::CENTER) {
//change data to "joystick center" data to stop the motors
dataRead = dataCenter;
ESP_LOGE(TAG, "TIMEOUT - no data received for 3s -> set to center");
}
}
return dataRead;
}
//--------------------------------------------
//--- receiveHttpData for httpJoystickMain ---
//--------------------------------------------
//function that wraps pointer to member function of httpJoystickMain instance in a "normal" function which the webserver can run on joystick URL
//declare pointer to receiveHttpData method of httpJoystick class
esp_err_t (httpJoystick::*pointerToReceiveFunc)(httpd_req_t *req) = &httpJoystick::receiveHttpData;
esp_err_t on_joystick_url(httpd_req_t *req){
//run pointer to receiveHttpData function of httpJoystickMain instance
return (httpJoystickMain.*pointerToReceiveFunc)(req);
}
//============================ //============================
@ -169,9 +231,6 @@ esp_err_t on_joystick_url(httpd_req_t *req)
void http_init_server() void http_init_server()
{ {
//configure webserver //configure webserver
httpd_config_t config = HTTPD_DEFAULT_CONFIG(); httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.uri_match_fn = httpd_uri_match_wildcard; config.uri_match_fn = httpd_uri_match_wildcard;

View File

@ -1,6 +1,14 @@
#pragma once #pragma once
extern QueueHandle_t joystickDataQueue; extern "C"
{
#include "esp_http_server.h"
}
#include "joystick.hpp"
//============================ //============================
//===== init http server ===== //===== init http server =====
@ -21,3 +29,43 @@ void start_mdns_service();
//============================ //============================
//function that destroys the http server //function that destroys the http server
void http_stop_server(); void http_stop_server();
//==============================
//===== httpJoystick class =====
//==============================
//class that receices that from a HTTP post request, generates and scales joystick data and provides the data in a queue
//struct with configuration parameters
typedef struct httpJoystick_config_t {
float toleranceZeroX_Per;//percentage around joystick axis the coordinate snaps to 0
float toleranceZeroY_Per;
float toleranceEndPer; //percentage before joystick end the coordinate snaps to 1/-1
uint32_t timeoutMs; //time no new data was received before the motors get turned off
} httpJoystick_config_t;
class httpJoystick{
public:
//--- constructor ---
httpJoystick( httpJoystick_config_t config_f );
//--- functions ---
joystickData_t getData(); //wait for and return joystick data from queue, if timeout return CENTER
esp_err_t receiveHttpData(httpd_req_t *req); //function that is called when data is received with post request at /api/joystick
private:
//--- variables ---
httpJoystick_config_t config;
QueueHandle_t joystickDataQueue = xQueueCreate( 1, sizeof( struct joystickData_t ) );
//struct for receiving data from http function, and storing data of last update
joystickData_t dataRead;
const joystickData_t dataCenter = {
.position = joystickPos_t::CENTER,
.x = 0,
.y = 0,
.radius = 0,
.angle = 0
};
};

View File

@ -68,50 +68,6 @@ int evaluatedJoystick::readAdc(adc1_channel_t adc_channel, bool inverted) {
//----------------------------
//------ getCoordinate -------
//----------------------------
//function to read voltage at a gpio pin and scale it to a value from -1 to 1 using the given thresholds and tolerances
float evaluatedJoystick::getCoordinate(adc1_channel_t adc_channel, bool inverted, int min, int max, int center, int tolerance_zero, int tolerance_end) {
float coordinate = 0;
//read voltage from adc
int input = readAdc(adc_channel, inverted);
//define coordinate value considering the different tolerances
//--- center ---
if ((input < center+tolerance_zero) && (input > center-tolerance_zero) ) { //adc value is inside tolerance around center threshold
coordinate = 0;
}
//--- maximum ---
else if (input > max-tolerance_end) {
coordinate = 1;
}
//--- minimum ---
else if (input < min+tolerance_end) {
coordinate = -1;
}
//--- positive area ---
else if (input > center) {
float range = max - center - tolerance_zero - tolerance_end;
coordinate = (input - center - tolerance_end) / range;
}
//--- negative area ---
else if (input < center) {
float range = (center - min - tolerance_zero - tolerance_end);
coordinate = -(center-input - tolerance_end) / range;
}
ESP_LOGD(TAG, "coordinate=%.3f, input=%d/4095, isInverted=%d", coordinate, input, inverted);
//return coordinate (-1 to 1)
return coordinate;
}
//------------------------------- //-------------------------------
//---------- getData ------------ //---------- getData ------------
//------------------------------- //-------------------------------
@ -120,10 +76,11 @@ joystickData_t evaluatedJoystick::getData() {
//get coordinates //get coordinates
//TODO individual tolerances for each axis? Otherwise some parameters can be removed //TODO individual tolerances for each axis? Otherwise some parameters can be removed
ESP_LOGD(TAG, "getting X coodrinate..."); ESP_LOGD(TAG, "getting X coodrinate...");
float x = getCoordinate(config.adc_x, config.x_inverted, config.x_min, config.x_max, x_center, config.tolerance_zero, config.tolerance_end); float x = scaleCoordinate(readAdc(config.adc_x, config.x_inverted), config.x_min, config.x_max, x_center, config.tolerance_zeroX_per, config.tolerance_end_per);
data.x = x; data.x = x;
ESP_LOGD(TAG, "getting Y coodrinate..."); ESP_LOGD(TAG, "getting Y coodrinate...");
float y = getCoordinate(config.adc_y, config.y_inverted, config.y_min, config.y_max, y_center, config.tolerance_zero, config.tolerance_end); float y = scaleCoordinate(readAdc(config.adc_y, config.y_inverted), config.y_min, config.y_max, y_center, config.tolerance_zeroY_per, config.tolerance_end_per);
data.y = y; data.y = y;
//calculate radius //calculate radius
@ -159,6 +116,53 @@ void evaluatedJoystick::defineCenter(){
//==============================
//====== scaleCoordinate =======
//==============================
//function that scales an input value (e.g. from adc pin) to a value from -1 to 1 using the given thresholds and tolerances
float scaleCoordinate(float input, float min, float max, float center, float tolerance_zero_per, float tolerance_end_per) {
float coordinate = 0;
//convert tolerance percentages to actual values of range
double tolerance_zero = (max-min) * tolerance_zero_per / 100;
double tolerance_end = (max-min) * tolerance_end_per / 100;
//define coordinate value considering the different tolerances
//--- center ---
if ((input < center+tolerance_zero) && (input > center-tolerance_zero) ) { //adc value is inside tolerance around center threshold
coordinate = 0;
}
//--- maximum ---
else if (input > max-tolerance_end) {
coordinate = 1;
}
//--- minimum ---
else if (input < min+tolerance_end) {
coordinate = -1;
}
//--- positive area ---
else if (input > center) {
float range = max - center - tolerance_zero - tolerance_end;
coordinate = (input - center - tolerance_zero) / range;
}
//--- negative area ---
else if (input < center) {
float range = (center - min - tolerance_zero - tolerance_end);
coordinate = -(center-input - tolerance_zero) / range;
}
ESP_LOGD(TAG, "scaled coordinate from %.3f to %.3f, tolZero=%.3f, tolEnd=%.3f", input, coordinate, tolerance_zero, tolerance_end);
//return coordinate (-1 to 1)
return coordinate;
}
//============================================= //=============================================
//========= joystick_evaluatePosition ========= //========= joystick_evaluatePosition =========
//============================================= //=============================================

View File

@ -31,10 +31,11 @@ typedef struct joystick_config_t {
adc1_channel_t adc_x; adc1_channel_t adc_x;
adc1_channel_t adc_y; adc1_channel_t adc_y;
//range around center-threshold of each axis the coordinates stays at 0 (adc value 0-4095) //percentage of joystick range the coordinate of the axis snaps to 0 (0-100)
int tolerance_zero; int tolerance_zeroX_per;
//threshold the coordinate snaps to -1 or 1 before configured "_max" or "_min" threshold (mechanical end) is reached (adc value 0-4095) int tolerance_zeroY_per;
int tolerance_end; //percentage of joystick range the coordinate snaps to -1 or 1 before configured "_max" or "_min" threshold (mechanical end) is reached (0-100)
int tolerance_end_per;
//threshold the radius jumps to 1 before the stick is at max radius (range 0-1) //threshold the radius jumps to 1 before the stick is at max radius (range 0-1)
float tolerance_radius; float tolerance_radius;
@ -86,8 +87,6 @@ class evaluatedJoystick {
void init(); void init();
//read adc while making multiple samples with option to invert the result //read adc while making multiple samples with option to invert the result
int readAdc(adc1_channel_t adc_channel, bool inverted = false); int readAdc(adc1_channel_t adc_channel, bool inverted = false);
//read input voltage and scale to value from -1 to 1 using the given thresholds and tolerances
float getCoordinate(adc1_channel_t adc_channel, bool inverted, int min, int max, int center, int tolerance_zero, int tolerance_end);
//--- variables --- //--- variables ---
joystick_config_t config; joystick_config_t config;
@ -111,9 +110,15 @@ class evaluatedJoystick {
motorCommands_t joystick_generateCommandsDriving(joystickData_t data ); motorCommands_t joystick_generateCommandsDriving(joystickData_t data );
//==============================
//====== scaleCoordinate =======
//==============================
//function that scales an input value (e.g. from adc pin) to a value from -1 to 1 using the giben thresholds and tolerances
float scaleCoordinate(float input, float min, float max, float center, float tolerance_zero_per, float tolerance_end_per);
//============================================
//========= joystick_CommandsDriving ========= //=============================================
//============================================ //========= joystick_evaluatePosition =========
//=============================================
//function that defines and returns enum joystickPos from x and y coordinates //function that defines and returns enum joystickPos from x and y coordinates
joystickPos_t joystick_evaluatePosition(float x, float y); joystickPos_t joystick_evaluatePosition(float x, float y);

View File

@ -169,7 +169,7 @@ extern "C" void app_main(void) {
//--- create task for control --- //--- create task for control ---
//------------------------------- //-------------------------------
//task that generates motor commands depending on the current mode and sends those to motorctl task //task that generates motor commands depending on the current mode and sends those to motorctl task
xTaskCreate(&task_control, "task_control", 2048, NULL, 5, NULL); xTaskCreate(&task_control, "task_control", 4096, NULL, 5, NULL);
//------------------------------ //------------------------------
//--- create task for button --- //--- create task for button ---

View File

@ -10,7 +10,7 @@
/> />
<title>armchair ctl</title> <title>armchair ctl</title>
</head> </head>
<body> <body style="background-color:#001427; color:white;">
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<!-- <!--

46
react-app/src/App.js vendored
View File

@ -7,11 +7,9 @@ import React, { useState} from 'react';
function App() { function App() {
//declare variables that can be used and updated in html //declare variables that can be used and updated in html
const [angle_html, setAngle_html] = useState(0);
const [x_html, setX_html] = useState(0); const [x_html, setX_html] = useState(0);
const [y_html, setY_html] = useState(0); const [y_html, setY_html] = useState(0);
const [ip, setIp] = useState("10.0.0.66"); const [ip, setIp] = useState("10.0.0.66");
const [radius_html, setRadius_html] = useState(0);
@ -19,7 +17,7 @@ function App() {
//=========== config ============ //=========== config ============
//=============================== //===============================
const decimalPlaces = 3; const decimalPlaces = 3;
const joystickSize = 200; //affects scaling of coordinates and size of joystick on website const joystickSize = 250; //affects scaling of coordinates and size of joystick on website
const throttle = 300; //throtthe interval the joystick sends data while moving (ms) const throttle = 300; //throtthe interval the joystick sends data while moving (ms)
const toleranceSnapToZeroPer = 20;//percentage of moveable range the joystick can be moved from the axix and value stays at 0 const toleranceSnapToZeroPer = 20;//percentage of moveable range the joystick can be moved from the axix and value stays at 0
@ -33,7 +31,7 @@ function App() {
// - snaps 0 zero for a given tolerance in percent // - snaps 0 zero for a given tolerance in percent
// - rounds value do given decimal places // - rounds value do given decimal places
// - TODO: add threshold it snaps to 1 / -1 (100%) toleranceEnd // - TODO: add threshold it snaps to 1 / -1 (100%) toleranceEnd
const ScaleCoordinate = (input) => { const ScaleCoordinateTolerance = (input) => {
//calc tolerance threshold and available range //calc tolerance threshold and available range
const tolerance = joystickSize/2 * toleranceSnapToZeroPer/100; const tolerance = joystickSize/2 * toleranceSnapToZeroPer/100;
const range = joystickSize/2 - tolerance; const range = joystickSize/2 - tolerance;
@ -61,6 +59,15 @@ function App() {
//----------------------------------------
//----------- Scale coordinate -----------
//----------------------------------------
//simply scale coordinate from joystick to a value of -1 to 1 without applying any tolerances
const ScaleCoordinate = (input) => {
return ( input / (joystickSize/2) ).toFixed(decimalPlaces);
}
//------------------------------------------- //-------------------------------------------
//------- Senda data via POST request ------- //------- Senda data via POST request -------
@ -106,27 +113,26 @@ function App() {
//evaluate coordinates and send to esp32 //evaluate coordinates and send to esp32
const handleMove = (e) => { const handleMove = (e) => {
//console.log("data from joystick-element X:" + e.x + " Y:" + e.y + " distance:" + e.distance); //console.log("data from joystick-element X:" + e.x + " Y:" + e.y + " distance:" + e.distance);
//calculate needed variables
//--- convert coordinates ---
//Note: tolerance (snap to zero) now handled by controller -> send raw coordinates
//const x = ScaleCoordinateTolerance(e.x);
//const y = ScaleCoordinateTolerance(e.y);
const x = ScaleCoordinate(e.x); const x = ScaleCoordinate(e.x);
const y = ScaleCoordinate(e.y); const y = ScaleCoordinate(e.y);
const radius = (e.distance / 100).toFixed(5);
const angle = ( Math.atan( y / x ) * 180 / Math.PI ).toFixed(2);
//crate object with necessary data //create object with necessary data
const joystick_data={ const joystick_data={
x: x, x: x,
y: y, y: y
radius: radius,
angle: angle
} }
//send object with joystick data as json to controller //send object with joystick data as json to controller
httpSendObject(joystick_data); httpSendObject(joystick_data);
//update variables for html //update variables for html
setX_html(joystick_data.x); setX_html(joystick_data.x);
setY_html(joystick_data.y); setY_html(joystick_data.y);
setRadius_html(joystick_data.radius);
setAngle_html(joystick_data.angle);
}; };
@ -139,15 +145,11 @@ function App() {
const joystick_data={ const joystick_data={
x: 0, x: 0,
y: 0, y: 0,
radius: 0,
angle: 0
} }
//update variables for html //update variables for html
setX_html(0); setX_html(0);
setY_html(0); setY_html(0);
setRadius_html(0);
setAngle_html(0);
//send object with joystick data as json to controller //send object with joystick data as json to controller
httpSendObject(joystick_data); httpSendObject(joystick_data);
}; };
@ -162,14 +164,14 @@ function App() {
<div style={{display:'flex', justifyContent:'center', alignItems:'center', height:'100vh'}}> <div style={{display:'flex', justifyContent:'center', alignItems:'center', height:'100vh'}}>
<div> <div>
<div style={{position: 'absolute', top: '0'}}> <div style={{position: 'absolute', top: '0', left: '0', width: '100%'}}>
<h1>Joystick ctl</h1> <h1 style={{width:'100%', textAlign:'center'}}>Armchair ctl</h1>
</div> </div>
<Joystick <Joystick
size={joystickSize} size={joystickSize}
sticky={false} sticky={false}
baseColor="red" baseColor="#8d0801"
stickColor="blue" stickColor="#708d81"
throttle={throttle} throttle={throttle}
move={handleMove} move={handleMove}
stop={handleStop} stop={handleStop}
@ -178,8 +180,6 @@ function App() {
<ul> <ul>
<li> x={x_html} </li> <li> x={x_html} </li>
<li> y={y_html} </li> <li> y={y_html} </li>
<li> radius={radius_html} </li>
<li> angle={angle_html} </li>
</ul> </ul>
</div> </div>
</div> </div>