Change folder structure (multiple boards)

- add second pcb board_control, currently copy of previous board
- not enough pins -> board_control handle ui send motorcommands via uart
  board_motorctl handle motors
This commit is contained in:
jonny_jr9
2023-08-28 11:15:06 +02:00
parent 224ac47214
commit b03baa4687
69 changed files with 32730 additions and 1399 deletions

View File

@@ -0,0 +1,20 @@
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"
"auto.cpp"
"currentsensor.cpp"
INCLUDE_DIRS
"."
)
spiffs_create_partition_image(spiffs ../react-app/build FLASH_IN_PROJECT)

View File

@@ -0,0 +1,88 @@
#include "auto.hpp"
#include "config.hpp"
//tag for logging
static const char * TAG = "automatedArmchair";
//=============================
//======== constructor ========
//=============================
automatedArmchair::automatedArmchair(void) {
//create command queue
commandQueue = xQueueCreate( 32, sizeof( commandSimple_t ) ); //TODO add max size to config?
}
//==============================
//====== generateCommands ======
//==============================
motorCommands_t automatedArmchair::generateCommands(auto_instruction_t * instruction) {
//reset instruction
*instruction = auto_instruction_t::NONE;
//check if previous command is finished
if ( esp_log_timestamp() > timestampCmdFinished ) {
//get next command from queue
if( xQueueReceive( commandQueue, &cmdCurrent, pdMS_TO_TICKS(500) ) ) {
ESP_LOGI(TAG, "running next command from queue...");
//copy instruction to be provided to control task
*instruction = cmdCurrent.instruction;
//set acceleration / fading parameters according to command
motorLeft.setFade(fadeType_t::DECEL, cmdCurrent.fadeDecel);
motorRight.setFade(fadeType_t::DECEL, cmdCurrent.fadeDecel);
motorLeft.setFade(fadeType_t::ACCEL, cmdCurrent.fadeAccel);
motorRight.setFade(fadeType_t::ACCEL, cmdCurrent.fadeAccel);
//calculate timestamp the command is finished
timestampCmdFinished = esp_log_timestamp() + cmdCurrent.msDuration;
//copy the new commands
motorCommands = cmdCurrent.motorCmds;
} else { //queue empty
ESP_LOGD(TAG, "no new command in queue -> set motors to IDLE");
motorCommands = motorCmds_bothMotorsIdle;
}
} else { //previous command still running
ESP_LOGD(TAG, "command still running -> no change");
}
//TODO also return instructions via call by reference
return motorCommands;
}
//============================
//======== addCommand ========
//============================
//function that adds a basic command to the queue
void automatedArmchair::addCommand(commandSimple_t command) {
//add command to queue
if ( xQueueSend( commandQueue, ( void * )&command, ( TickType_t ) 0 ) ){
ESP_LOGI(TAG, "Successfully inserted command to queue");
} else {
ESP_LOGE(TAG, "Failed to insert new command to queue");
}
}
void automatedArmchair::addCommands(commandSimple_t commands[], size_t count) {
for (int i = 0; i < count; i++) {
ESP_LOGI(TAG, "Reading command no. %d from provided array", i);
addCommand(commands[i]);
}
}
//===============================
//======== clearCommands ========
//===============================
//function that deletes all pending/queued commands
//e.g. when switching modes
motorCommands_t automatedArmchair::clearCommands() {
//clear command queue
xQueueReset( commandQueue );
ESP_LOGW(TAG, "command queue was successfully emptied");
//return commands for idling both motors
motorCommands = motorCmds_bothMotorsIdle;
return motorCmds_bothMotorsIdle;
}

View File

@@ -0,0 +1,87 @@
#pragma once
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
}
#include "freertos/queue.h"
#include <cmath>
#include "motorctl.hpp"
//--------------------------------------------
//---- struct, enum, variable declarations ---
//--------------------------------------------
//enum for special instructions / commands to be run in control task
enum class auto_instruction_t { NONE, SWITCH_PREV_MODE, SWITCH_JOYSTICK_MODE, RESET_ACCEL_DECEL, RESET_ACCEL, RESET_DECEL };
//struct for a simple command
//e.g. put motors in a certain state for certain time
typedef struct commandSimple_t{
motorCommands_t motorCmds;
uint32_t msDuration;
uint32_t fadeDecel;
uint32_t fadeAccel;
auto_instruction_t instruction;
} commandSimple_t;
//------------------------------------
//----- automatedArmchair class -----
//------------------------------------
class automatedArmchair {
public:
//--- methods ---
//constructor
automatedArmchair(void);
//function to generate motor commands
//can be also seen as handle function
//TODO: go with other approach: separate task for handling auto mode
// - receive commands with queue anyways
// - => use delay function
// - have a queue that outputs current motor state/commands -> repeatedly check the queue in control task
//function that handles automatic driving and returns motor commands
//also provides instructions to be executed in control task via pointer
motorCommands_t generateCommands(auto_instruction_t * instruction);
//function that adds a basic command to the queue
void addCommand(commandSimple_t command);
//function that adds an array of basic commands to queue
void addCommands(commandSimple_t commands[], size_t count);
//function that deletes all pending/queued commands
motorCommands_t clearCommands();
private:
//--- methods ---
//--- objects ---
//TODO: add buzzer here
//--- variables ---
//queue for storing pending commands
QueueHandle_t commandQueue = NULL;
//current running command
commandSimple_t cmdCurrent;
//timestamp current command is finished
uint32_t timestampCmdFinished = 0;
motorCommands_t motorCommands;
//command preset for idling motors
const motorCommand_t motorCmd_motorIdle = {
.state = motorstate_t::IDLE,
.duty = 0
};
const motorCommands_t motorCmds_bothMotorsIdle = {
.left = motorCmd_motorIdle,
.right = motorCmd_motorIdle
};
};

View File

@@ -0,0 +1,215 @@
extern "C"
{
#include <stdio.h>
#include <esp_system.h>
#include <esp_event.h>
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
}
#include "button.hpp"
//tag for logging
static const char * TAG = "button";
//-----------------------------
//-------- constructor --------
//-----------------------------
buttonCommands::buttonCommands(gpio_evaluatedSwitch * button_f, evaluatedJoystick * joystick_f, controlledArmchair * control_f, buzzer_t * buzzer_f, controlledMotor * motorLeft_f, controlledMotor * motorRight_f){
//copy object pointers
button = button_f;
joystick = joystick_f;
control = control_f;
buzzer = buzzer_f;
motorLeft = motorLeft_f;
motorRight = motorRight_f;
//TODO declare / configure evaluatedSwitch here instead of config (unnecessary that button object is globally available - only used here)?
}
//----------------------------
//--------- action -----------
//----------------------------
//function that runs commands depending on a count value
void buttonCommands::action (uint8_t count, bool lastPressLong){
//--- variable declarations ---
bool decelEnabled; //for different beeping when toggling
commandSimple_t cmds[8]; //array for commands for automatedArmchair
//--- get joystick position ---
//joystickData_t stickData = joystick->getData();
//--- actions based on count ---
switch (count){
//no such command
default:
ESP_LOGE(TAG, "no command for count=%d defined", count);
buzzer->beep(3, 400, 100);
break;
case 1:
//restart contoller when 1x long pressed
if (lastPressLong){
ESP_LOGW(TAG, "RESTART");
buzzer->beep(1,1000,1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_restart();
return;
}
ESP_LOGW(TAG, "cmd %d: sending button event to control task", count);
//-> define joystick center or toggle freeze input (executed in control task)
control->sendButtonEvent(count); //TODO: always send button event to control task (not just at count=1) -> control.cpp has to be changed
break;
case 2:
//run automatic commands to lift leg support when pressed 1x short 1x long
if (lastPressLong){
//define commands
cmds[0] =
{
.motorCmds = {
.left = {motorstate_t::REV, 90},
.right = {motorstate_t::REV, 90}
},
.msDuration = 1200,
.fadeDecel = 800,
.fadeAccel = 1300,
.instruction = auto_instruction_t::NONE
};
cmds[1] =
{
.motorCmds = {
.left = {motorstate_t::FWD, 70},
.right = {motorstate_t::FWD, 70}
},
.msDuration = 70,
.fadeDecel = 0,
.fadeAccel = 300,
.instruction = auto_instruction_t::NONE
};
cmds[2] =
{
.motorCmds = {
.left = {motorstate_t::IDLE, 0},
.right = {motorstate_t::IDLE, 0}
},
.msDuration = 10,
.fadeDecel = 800,
.fadeAccel = 1300,
.instruction = auto_instruction_t::SWITCH_JOYSTICK_MODE
};
//send commands to automatedArmchair command queue
armchair.addCommands(cmds, 3);
//change mode to AUTO
control->changeMode(controlMode_t::AUTO);
return;
}
//toggle idle when 2x pressed
ESP_LOGW(TAG, "cmd %d: toggle IDLE", count);
control->toggleIdle(); //toggle between idle and previous/default mode
break;
case 3:
ESP_LOGW(TAG, "cmd %d: switch to JOYSTICK", count);
control->changeMode(controlMode_t::JOYSTICK); //switch to JOYSTICK 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
break;
case 8:
//toggle deceleration fading between on and off
decelEnabled = motorLeft->toggleFade(fadeType_t::DECEL);
motorRight->toggleFade(fadeType_t::DECEL);
ESP_LOGW(TAG, "cmd %d: toggle deceleration fading to: %d", count, (int)decelEnabled);
if (decelEnabled){
buzzer->beep(3, 60, 50);
} else {
buzzer->beep(1, 1000, 1);
}
break;
case 12:
ESP_LOGW(TAG, "cmd %d: sending button event to control task", count);
//-> toggle altStickMapping (executed in control task)
control->sendButtonEvent(count); //TODO: always send button event to control task (not just at count=1)?
break;
}
}
//-----------------------------
//------ startHandleLoop ------
//-----------------------------
//this function has to be started once in a separate task
//repeatedly evaluates and processes button events then takes the corresponding action
void buttonCommands::startHandleLoop() {
while(1) {
vTaskDelay(20 / portTICK_PERIOD_MS);
//run handle function of evaluatedSwitch object
button->handle();
//--- count button presses and run action ---
switch(state) {
case inputState_t::IDLE: //wait for initial button press
if (button->risingEdge) {
count = 1;
buzzer->beep(1, 65, 0);
timestamp_lastAction = esp_log_timestamp();
state = inputState_t::WAIT_FOR_INPUT;
ESP_LOGI(TAG, "first button press detected -> waiting for further events");
}
break;
case inputState_t::WAIT_FOR_INPUT: //wait for further presses
//button pressed again
if (button->risingEdge){
count++;
buzzer->beep(1, 65, 0);
timestamp_lastAction = esp_log_timestamp();
ESP_LOGI(TAG, "another press detected -> count=%d -> waiting for further events", count);
}
//timeout
else if (esp_log_timestamp() - timestamp_lastAction > 1000) {
state = inputState_t::IDLE;
buzzer->beep(count, 50, 50);
//TODO: add optional "bool wait" parameter to beep function to delay until finished beeping
ESP_LOGI(TAG, "timeout - running action function for count=%d", count);
//--- run action function ---
//check if still pressed
bool lastPressLong = false;
if (button->state == true){
//run special case when last press was longer than timeout
lastPressLong = true;
}
//run action function with current count of button presses
action(count, lastPressLong);
}
break;
}
}
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include "gpio_evaluateSwitch.hpp"
#include "buzzer.hpp"
#include "control.hpp"
#include "motorctl.hpp"
#include "auto.hpp"
#include "config.hpp"
#include "joystick.hpp"
//===================================
//====== buttonCommands class =======
//===================================
//class which runs commands depending on the count a button was pressed
class buttonCommands {
public:
//--- constructor ---
buttonCommands (
gpio_evaluatedSwitch * button_f,
evaluatedJoystick * joystick_f,
controlledArmchair * control_f,
buzzer_t * buzzer_f,
controlledMotor * motorLeft_f,
controlledMotor * motorRight_f
);
//--- functions ---
//the following function has to be started once in a separate task.
//repeatedly evaluates and processes button events then takes the corresponding action
void startHandleLoop();
private:
//--- functions ---
void action(uint8_t count, bool lastPressLong);
//--- objects ---
gpio_evaluatedSwitch* button;
evaluatedJoystick* joystick;
controlledArmchair * control;
buzzer_t* buzzer;
controlledMotor * motorLeft;
controlledMotor * motorRight;
//--- variables ---
uint8_t count = 0;
uint32_t timestamp_lastAction = 0;
enum class inputState_t {IDLE, WAIT_FOR_INPUT};
inputState_t state = inputState_t::IDLE;
};

View File

@@ -0,0 +1,95 @@
#include "buzzer.hpp"
#include "config.hpp"
static const char *TAG_BUZZER = "buzzer";
//============================
//========== init ============
//============================
//define gpio pin as output, initialize queue
void buzzer_t::init(){
//define buzzer pin as output
gpio_pad_select_gpio(gpio_pin);
gpio_set_direction(gpio_pin, GPIO_MODE_OUTPUT);
//create queue
beepQueue = xQueueCreate( 20, sizeof( struct beepEntry ) );
}
//=============================
//======== constructor ========
//=============================
//copy provided config parameters to private variables, run init function
buzzer_t::buzzer_t(gpio_num_t gpio_pin_f, uint16_t msGap_f){
ESP_LOGI(TAG_BUZZER, "Initializing buzzer");
//copy configuration parameters to variables
gpio_pin = gpio_pin_f;
msGap = msGap_f;
//run init function to initialize gpio and queue
init();
};
//============================
//=========== beep ===========
//============================
//function to add a beep command to the queue
void buzzer_t::beep(uint8_t count, uint16_t msOn, uint16_t msOff){
//create entry struct with provided data
struct beepEntry entryInsert = {
count = count,
msOn = msOn,
msOff = msOff
};
// Send a pointer to a struct AMessage object. Don't block if the
// queue is already full.
//struct beepEntry *entryInsertPointer;
//entryInsertPointer = &entryInsertData;
ESP_LOGW(TAG_BUZZER, "Inserted object to queue - count=%d, msOn=%d, msOff=%d", entryInsert.count, entryInsert.msOn, entryInsert.msOff);
//xQueueGenericSend( beepQueue, ( void * ) &entryInsertPointer, ( TickType_t ) 0, queueSEND_TO_BACK );
xQueueSend( beepQueue, ( void * )&entryInsert, ( TickType_t ) 0 );
}
//==============================
//======== processQueue ========
//==============================
void buzzer_t::processQueue(){
//struct for receiving incomming events
struct beepEntry entryRead = { };
//loop forever
while(1){
ESP_LOGD(TAG_BUZZER, "processQueue: waiting for beep command");
//if queue is ready
if( beepQueue != 0 )
{
// wait for a queue entry to be available indefinetely if INCLUDE_vTaskSuspend is enabled in the FreeRTOS config
// otherwise waits for at least 7 weeks
if( xQueueReceive( beepQueue, &entryRead, portMAX_DELAY ) )
{
ESP_LOGW(TAG_BUZZER, "Read entry from queue: count=%d, msOn=%d, msOff=%d", entryRead.count, entryRead.msOn, entryRead.msOff);
//beep requested count with requested delays
for (int i = entryRead.count; i--;){
//turn on
ESP_LOGD(TAG_BUZZER, "turning buzzer on");
gpio_set_level(gpio_pin, 1);
vTaskDelay(entryRead.msOn / portTICK_PERIOD_MS);
//turn off
ESP_LOGD(TAG_BUZZER, "turning buzzer off");
gpio_set_level(gpio_pin, 0);
vTaskDelay(entryRead.msOff / portTICK_PERIOD_MS);
}
//wait for minimum gap between beep events
vTaskDelay(msGap / portTICK_PERIOD_MS);
}
}else{ //wait for queue to become available
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <stdio.h>
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
}
#include "freertos/queue.h"
//===================================
//========= buzzer_t class ==========
//===================================
//class which blinks a gpio pin for the provided count and durations.
//- 'processQueue' has to be run in a separate task
//- uses a queue to queue up multiple beep commands
class buzzer_t {
public:
//--- constructor ---
buzzer_t(gpio_num_t gpio_pin_f, uint16_t msGap_f = 200);
//--- functions ---
void processQueue(); //has to be run once in a separate task, waits for and processes queued events
void beep(uint8_t count, uint16_t msOn, uint16_t msOff);
//void clear(); (TODO - not implemented yet)
//void createTask(); (TODO - not implemented yet)
//--- variables ---
uint16_t msGap; //gap between beep entries (when multiple queued)
private:
//--- functions ---
void init();
//--- variables ---
gpio_num_t gpio_pin;
struct beepEntry {
uint8_t count;
uint16_t msOn;
uint16_t msOff;
};
//queue for queueing up multiple events while one is still processing
QueueHandle_t beepQueue = NULL;
};

View File

@@ -0,0 +1,151 @@
#include "config.hpp"
//===================================
//======= motor configuration =======
//===================================
//--- configure left motor (hardware) ---
single100a_config_t configDriverLeft = {
.gpio_pwm = GPIO_NUM_26,
.gpio_a = GPIO_NUM_16,
.gpio_b = GPIO_NUM_4,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.aEnabledPinState = false, //-> pins inverted (mosfets)
.bEnabledPinState = false,
.resolution = LEDC_TIMER_11_BIT,
.pwmFreq = 10000
};
//--- configure right motor (hardware) ---
single100a_config_t configDriverRight = {
.gpio_pwm = GPIO_NUM_27,
.gpio_a = GPIO_NUM_2,
.gpio_b = GPIO_NUM_14,
.ledc_timer = LEDC_TIMER_1,
.ledc_channel = LEDC_CHANNEL_1,
.aEnabledPinState = false, //-> pin inverted (mosfet)
.bEnabledPinState = true, //-> not inverted (direct)
.resolution = LEDC_TIMER_11_BIT,
.pwmFreq = 10000
};
//TODO add motor name string -> then use as log tag?
//--- configure left motor (contol) ---
motorctl_config_t configMotorControlLeft = {
.msFadeAccel = 1900, //acceleration of the motor (ms it takes from 0% to 100%)
.msFadeDecel = 1000, //deceleration of the motor (ms it takes from 100% to 0%)
.currentLimitEnabled = true,
.currentSensor_adc = ADC1_CHANNEL_6, //GPIO34
.currentSensor_ratedCurrent = 50,
.currentMax = 30,
.deadTimeMs = 900 //minimum time motor is off between direction change
};
//--- configure right motor (contol) ---
motorctl_config_t configMotorControlRight = {
.msFadeAccel = 1900, //acceleration of the motor (ms it takes from 0% to 100%)
.msFadeDecel = 1000, //deceleration of the motor (ms it takes from 100% to 0%)
.currentLimitEnabled = true,
.currentSensor_adc = ADC1_CHANNEL_4, //GPIO32
.currentSensor_ratedCurrent = 50,
.currentMax = 30,
.deadTimeMs = 900 //minimum time motor is off between direction change
};
//==============================
//======= 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 = 1, //percentage around joystick axis the coordinate snaps to 0
.toleranceZeroY_Per = 6,
.toleranceEndPer = 2, //percentage before joystick end the coordinate snaps to 1/-1
.timeoutMs = 2500 //time no new data was received before the motors get turned off
};
//======================================
//======= joystick configuration =======
//======================================
joystick_config_t configJoystick = {
.adc_x = ADC1_CHANNEL_3, //GPIO39
.adc_y = ADC1_CHANNEL_0, //GPIO36
//percentage of joystick range the coordinate of the axis snaps to 0 (0-100)
.tolerance_zeroX_per = 7, //6
.tolerance_zeroY_per = 10, //7
//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 = 4,
//threshold the radius jumps to 1 before the stick is at max radius (range 0-1)
.tolerance_radius = 0.09,
//min and max adc values of each axis, !!!AFTER INVERSION!!! is applied:
.x_min = 1392, //=> x=-1
.x_max = 2650, //=> x=1
.y_min = 1390, //=> y=-1
.y_max = 2640, //=> y=1
//invert adc measurement
.x_inverted = true,
.y_inverted = true
};
//============================
//=== configure fan contol ===
//============================
fan_config_t configCooling = {
.gpio_fan = GPIO_NUM_13,
.dutyThreshold = 40,
.minOnMs = 1500,
.minOffMs = 3000,
.turnOffDelayMs = 5000,
};
//=================================
//===== create global objects =====
//=================================
//TODO outsource global variables to e.g. global.cpp and only config options here?
//create controlled motor instances (motorctl.hpp)
controlledMotor motorLeft(configDriverLeft, configMotorControlLeft);
controlledMotor motorRight(configDriverRight, configMotorControlRight);
//create global joystic instance (joystick.hpp)
evaluatedJoystick joystick(configJoystick);
//create global evaluated switch instance for button next to joystick
gpio_evaluatedSwitch buttonJoystick(GPIO_NUM_25, 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 (http.hpp)
httpJoystick httpJoystickMain(configHttpJoystickMain);
//create global control object (control.hpp)
controlledArmchair control(configControl, &buzzer, &motorLeft, &motorRight, &joystick, &httpJoystickMain);
//create global automatedArmchair object (for auto-mode) (auto.hpp)
automatedArmchair armchair;

View File

@@ -0,0 +1,46 @@
#pragma once
#include "motordrivers.hpp"
#include "motorctl.hpp"
#include "joystick.hpp"
#include "gpio_evaluateSwitch.hpp"
#include "buzzer.hpp"
#include "control.hpp"
#include "fan.hpp"
#include "http.hpp"
#include "auto.hpp"
//in IDLE mode: set loglevel for evaluatedJoystick to DEBUG
//and repeatedly read joystick e.g. for manually calibrating / testing joystick
//#define JOYSTICK_LOG_IN_IDLE
//TODO outsource global variables to e.g. global.cpp and only config options here?
//create global controlledMotor instances for both motors
extern controlledMotor motorLeft;
extern controlledMotor motorRight;
//create global joystic instance
extern evaluatedJoystick joystick;
//create global evaluated switch instance for button next to joystick
extern gpio_evaluatedSwitch buttonJoystick;
//create global buzzer object
extern buzzer_t buzzer;
//create global control object
extern controlledArmchair control;
//create global automatedArmchair object (for auto-mode)
extern automatedArmchair armchair;
//create global httpJoystick object
extern httpJoystick httpJoystickMain;
//configuration for fans / cooling
extern fan_config_t configCooling;

View File

@@ -0,0 +1,479 @@
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"
//used definitions moved from config.hpp:
//#define JOYSTICK_TEST
//tag for logging
static const char * TAG = "control";
const char* controlModeStr[7] = {"IDLE", "JOYSTICK", "MASSAGE", "HTTP", "MQTT", "BLUETOOTH", "AUTO"};
//-----------------------------
//-------- constructor --------
//-----------------------------
controlledArmchair::controlledArmchair (
control_config_t config_f,
buzzer_t * buzzer_f,
controlledMotor* motorLeft_f,
controlledMotor* motorRight_f,
evaluatedJoystick* joystick_f,
httpJoystick* httpJoystick_f
){
//copy configuration
config = config_f;
//copy object pointers
buzzer = buzzer_f;
motorLeft = motorLeft_f;
motorRight = motorRight_f;
joystick_l = joystick_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)?
}
//----------------------------------
//---------- Handle loop -----------
//----------------------------------
//function that repeatedly generates motor commands depending on the current mode
//also handles fading and current-limit
void controlledArmchair::startHandleLoop() {
while (1){
ESP_LOGV(TAG, "control task executing... mode=%s", controlModeStr[(int)mode]);
switch(mode) {
default:
mode = controlMode_t::IDLE;
break;
case controlMode_t::IDLE:
//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);
vTaskDelay(200 / portTICK_PERIOD_MS);
#ifdef JOYSTICK_LOG_IN_IDLE
//get joystick data here (without using it)
//since loglevel is DEBUG, calculateion details is output
joystick_l->getData(); //get joystick data here
#endif
break;
case controlMode_t::JOYSTICK:
vTaskDelay(20 / portTICK_PERIOD_MS);
//get current joystick data with getData method of evaluatedJoystick
stickData = joystick_l->getData();
//additionaly scale coordinates (more detail in slower area)
joystick_scaleCoordinatesLinear(&stickData, 0.6, 0.35); //TODO: add scaling parameters to config
//generate motor commands
commands = joystick_generateCommandsDriving(stickData, altStickMapping);
//apply motor commands
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
break;
case controlMode_t::MASSAGE:
vTaskDelay(10 / portTICK_PERIOD_MS);
//--- read joystick ---
//only update joystick data when input not frozen
if (!freezeInput){
stickData = joystick_l->getData();
}
//--- generate motor commands ---
//pass joystick data from getData method of evaluatedJoystick to generateCommandsShaking function
commands = joystick_generateCommandsShaking(stickData);
//apply motor commands
motorRight->setTarget(commands.right.state, commands.right.duty);
motorLeft->setTarget(commands.left.state, commands.left.duty);
break;
case controlMode_t::HTTP:
//--- get joystick data from queue ---
//Note this function waits several seconds (httpconfig.timeoutMs) for data to arrive, otherwise Center data or NULL is returned
//TODO: as described above, when changing modes it might delay a few seconds for the change to apply
stickData = httpJoystickMain_l->getData();
//scale coordinates additionally (more detail in slower area)
joystick_scaleCoordinatesLinear(&stickData, 0.6, 0.4); //TODO: add scaling parameters to config
ESP_LOGD(TAG, "generating commands from x=%.3f y=%.3f radius=%.3f angle=%.3f", stickData.x, stickData.y, stickData.radius, stickData.angle);
//--- generate motor commands ---
//Note: timeout (no data received) is handled in getData method
commands = joystick_generateCommandsDriving(stickData, altStickMapping);
//--- 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);
break;
case controlMode_t::AUTO:
vTaskDelay(20 / portTICK_PERIOD_MS);
//generate commands
commands = armchair.generateCommands(&instruction);
//--- 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);
//process received instruction
switch (instruction) {
case auto_instruction_t::NONE:
break;
case auto_instruction_t::SWITCH_PREV_MODE:
toggleMode(controlMode_t::AUTO);
break;
case auto_instruction_t::SWITCH_JOYSTICK_MODE:
changeMode(controlMode_t::JOYSTICK);
break;
case auto_instruction_t::RESET_ACCEL_DECEL:
//enable downfading (set to default value)
motorLeft->setFade(fadeType_t::DECEL, true);
motorRight->setFade(fadeType_t::DECEL, true);
//set upfading to default value
motorLeft->setFade(fadeType_t::ACCEL, true);
motorRight->setFade(fadeType_t::ACCEL, true);
break;
case auto_instruction_t::RESET_ACCEL:
//set upfading to default value
motorLeft->setFade(fadeType_t::ACCEL, true);
motorRight->setFade(fadeType_t::ACCEL, true);
break;
case auto_instruction_t::RESET_DECEL:
//enable downfading (set to default value)
motorLeft->setFade(fadeType_t::DECEL, true);
motorRight->setFade(fadeType_t::DECEL, true);
break;
}
break;
//TODO: add other modes here
}
//--- run actions based on received button button event ---
//note: buttonCount received by sendButtonEvent method called from button.cpp
//TODO: what if variable gets set from other task during this code? -> mutex around this code
switch (buttonCount) {
case 1: //define joystick center or freeze input
if (mode == controlMode_t::JOYSTICK){
//joystick mode: calibrate joystick
joystick_l->defineCenter();
} else if (mode == controlMode_t::MASSAGE){
//massage mode: toggle freeze of input (lock joystick at current values)
freezeInput = !freezeInput;
if (freezeInput){
buzzer->beep(5, 40, 25);
} else {
buzzer->beep(1, 300, 100);
}
}
break;
case 12: //toggle alternative joystick mapping (reverse swapped)
altStickMapping = !altStickMapping;
if (altStickMapping){
buzzer->beep(6, 70, 50);
} else {
buzzer->beep(1, 500, 100);
}
break;
}
//--- reset button event --- (only one action per run)
if (buttonCount > 0){
ESP_LOGI(TAG, "resetting button event/count");
buttonCount = 0;
}
//-----------------------
//------ slow loop ------
//-----------------------
//this section is run about every 5s (+500ms)
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);
timestamp_SlowLoopLastRun = esp_log_timestamp();
//run function which detects timeout (switch to idle)
handleTimeout();
}
}//end while(1)
}//end startHandleLoop
//-----------------------------------
//---------- resetTimeout -----------
//-----------------------------------
void controlledArmchair::resetTimeout(){
//TODO mutex
timestamp_lastActivity = esp_log_timestamp();
}
//------------------------------------
//--------- sendButtonEvent ----------
//------------------------------------
void controlledArmchair::sendButtonEvent(uint8_t count){
//TODO mutex - if not replaced with queue
ESP_LOGI(TAG, "setting button event");
buttonCount = count;
}
//------------------------------------
//---------- handleTimeout -----------
//------------------------------------
//percentage the duty can vary since last timeout check and still counts as incative
//TODO: add this to config
float inactivityTolerance = 10;
//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: [activity] detected since last check -> reset");
//reset last duty and timestamp
dutyLeft_lastActivity = dutyLeftNow;
dutyRight_lastActivity = dutyRightNow;
resetTimeout();
}
//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 s ago, timeout after %d s", (float)(esp_log_timestamp() - timestamp_lastActivity)/1000, config.timeoutMs/1000);
}
}
}
//-----------------------------------
//----------- changeMode ------------
//-----------------------------------
//function to change to a specified control mode
void controlledArmchair::changeMode(controlMode_t modeNew) {
//reset timeout timer
resetTimeout();
//exit if target mode is already active
if (mode == modeNew) {
ESP_LOGE(TAG, "changeMode: Already in target mode '%s' -> nothing to change", controlModeStr[(int)mode]);
return;
}
//copy previous mode
modePrevious = mode;
ESP_LOGW(TAG, "=== changing mode from %s to %s ===", controlModeStr[(int)mode], controlModeStr[(int)modeNew]);
//========== commands change FROM mode ==========
//run functions when changing FROM certain mode
switch(modePrevious){
default:
ESP_LOGI(TAG, "noting to execute when changing FROM this mode");
break;
#ifdef JOYSTICK_LOG_IN_IDLE
case controlMode_t::IDLE:
ESP_LOGI(TAG, "disabling debug output for 'evaluatedJoystick'");
esp_log_level_set("evaluatedJoystick", ESP_LOG_WARN); //FIXME: loglevel from config
break;
#endif
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;
case controlMode_t::MASSAGE:
ESP_LOGW(TAG, "switching from MASSAGE mode -> restoring fading, reset frozen input");
//TODO: fix issue when downfading was disabled before switching to massage mode - currently it gets enabled again here...
//enable downfading (set to default value)
motorLeft->setFade(fadeType_t::DECEL, true);
motorRight->setFade(fadeType_t::DECEL, true);
//set upfading to default value
motorLeft->setFade(fadeType_t::ACCEL, true);
motorRight->setFade(fadeType_t::ACCEL, true);
//reset frozen input state
freezeInput = false;
break;
case controlMode_t::AUTO:
ESP_LOGW(TAG, "switching from AUTO mode -> restoring fading to default");
//TODO: fix issue when downfading was disabled before switching to auto mode - currently it gets enabled again here...
//enable downfading (set to default value)
motorLeft->setFade(fadeType_t::DECEL, true);
motorRight->setFade(fadeType_t::DECEL, true);
//set upfading to default value
motorLeft->setFade(fadeType_t::ACCEL, true);
motorRight->setFade(fadeType_t::ACCEL, true);
break;
}
//========== commands change TO mode ==========
//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::IDLE:
buzzer->beep(1, 1500, 0);
#ifdef JOYSTICK_LOG_IN_IDLE
esp_log_level_set("evaluatedJoystick", ESP_LOG_DEBUG);
#endif
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;
case controlMode_t::MASSAGE:
ESP_LOGW(TAG, "switching to MASSAGE mode -> reducing fading");
uint32_t shake_msFadeAccel = 500; //TODO: move this to config
//disable downfading (max. deceleration)
motorLeft->setFade(fadeType_t::DECEL, false);
motorRight->setFade(fadeType_t::DECEL, false);
//reduce upfading (increase acceleration)
motorLeft->setFade(fadeType_t::ACCEL, shake_msFadeAccel);
motorRight->setFade(fadeType_t::ACCEL, shake_msFadeAccel);
break;
}
//--- update mode to new mode ---
//TODO: add mutex
mode = modeNew;
}
//TODO simplify the following 3 functions? can be replaced by one?
//-----------------------------------
//----------- toggleIdle ------------
//-----------------------------------
//function to toggle between IDLE and previous active mode
void controlledArmchair::toggleIdle() {
//toggle between IDLE and previous mode
toggleMode(controlMode_t::IDLE);
}
//------------------------------------
//----------- toggleModes ------------
//------------------------------------
//function to toggle between two modes, but prefer first argument if entirely different mode is currently active
void controlledArmchair::toggleModes(controlMode_t modePrimary, controlMode_t modeSecondary) {
//switch to secondary mode when primary is already active
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);
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);
changeMode(modePrimary);
}
}
//-----------------------------------
//----------- toggleMode ------------
//-----------------------------------
//function that toggles between certain mode and previous mode
void controlledArmchair::toggleMode(controlMode_t modePrimary){
//switch to previous mode when primary is already active
if (mode == modePrimary){
ESP_LOGW(TAG, "toggleMode: switching from primaryMode %s to previousMode %s", controlModeStr[(int)mode], controlModeStr[(int)modePrevious]);
//buzzer->beep(2,200,100);
changeMode(modePrevious); //switch to previous 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);
changeMode(modePrimary);
}
}

View File

@@ -0,0 +1,130 @@
#pragma once
#include "motordrivers.hpp"
#include "motorctl.hpp"
#include "buzzer.hpp"
#include "http.hpp"
#include "auto.hpp"
//--------------------------------------------
//---- struct, enum, variable declarations ---
//--------------------------------------------
//enum that decides how the motors get controlled
enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, HTTP, MQTT, BLUETOOTH, AUTO};
//string array representing the mode enum (for printing the state as string)
extern const char* controlModeStr[7];
//--- control_config_t ---
//struct with config parameters
typedef struct control_config_t {
controlMode_t defaultMode; //default mode after startup and toggling IDLE
//timeout options
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;
//==================================
//========= control class ==========
//==================================
//controls the mode the armchair operates
//repeatedly generates the motor commands corresponding to current mode and sends those to motorcontrol
class controlledArmchair {
public:
//--- constructor ---
controlledArmchair (
control_config_t config_f,
buzzer_t* buzzer_f,
controlledMotor* motorLeft_f,
controlledMotor* motorRight_f,
evaluatedJoystick* joystick_f,
httpJoystick* httpJoystick_f
);
//--- functions ---
//task that repeatedly generates motor commands depending on the current mode
void startHandleLoop();
//function that changes to a specified control mode
void changeMode(controlMode_t modeNew);
//function that toggle between IDLE and previous active mode (or default if not switched to certain mode yet)
void toggleIdle();
//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);
//toggle between certain mode and previous mode
void toggleMode(controlMode_t modePrimary);
//function that restarts timer which initiates the automatic timeout (switch to IDLE) after certain time of inactivity
void resetTimeout();
//function for sending a button event (e.g. from button task at event) to control task
//TODO: use queue instead?
void sendButtonEvent(uint8_t count);
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 ---
buzzer_t* buzzer;
controlledMotor* motorLeft;
controlledMotor* motorRight;
httpJoystick* httpJoystickMain_l;
evaluatedJoystick* joystick_l;
//---variables ---
//struct for motor commands returned by generate functions of each mode
motorCommands_t commands;
//struct with config parameters
control_config_t config;
//store joystick data
joystickData_t stickData;
bool altStickMapping; //alternative joystick mapping (reverse mapped differently)
//variables for http mode
uint32_t http_timestamp_lastData = 0;
//variables for MASSAGE mode
bool freezeInput = false;
//variables for AUTO mode
auto_instruction_t instruction = auto_instruction_t::NONE; //variable to receive instructions from automatedArmchair
//variable to store button event
uint8_t buttonCount = 0;
//definition of mode enum
controlMode_t mode = controlMode_t::IDLE;
//variable to store mode when toggling IDLE mode
controlMode_t modePrevious; //default mode
//command preset for idling motors
const motorCommand_t cmd_motorIdle = {
.state = motorstate_t::IDLE,
.duty = 0
};
const motorCommands_t cmds_bothMotorsIdle = {
.left = 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

@@ -0,0 +1,75 @@
extern "C" {
#include "hal/timer_types.h"
#include "esp_log.h"
}
#include "currentsensor.hpp"
//tag for logging
static const char * TAG = "current-sensors";
//--------------------------
//------- getVoltage -------
//--------------------------
//local function to get average voltage from adc
float getVoltage(adc1_channel_t adc, uint32_t samples){
//measure voltage
int measure = 0;
for (int j=0; j<samples; j++){
measure += adc1_get_raw(adc);
ets_delay_us(50);
}
return (float)measure / samples / 4096 * 3.3;
}
//=============================
//======== constructor ========
//=============================
currentSensor::currentSensor (adc1_channel_t adcChannel_f, float ratedCurrent_f){
//copy config
adcChannel = adcChannel_f;
ratedCurrent = ratedCurrent_f;
//init adc
adc1_config_width(ADC_WIDTH_BIT_12); //max resolution 4096
adc1_config_channel_atten(adcChannel, ADC_ATTEN_DB_11); //max voltage
//calibrate
calibrateZeroAmpere();
}
//============================
//=========== read ===========
//============================
float currentSensor::read(void){
//measure voltage
voltage = getVoltage(adcChannel, 30);
//scale voltage to current
if (voltage < centerVoltage){
current = (1 - voltage / centerVoltage) * -ratedCurrent;
} else if (voltage > centerVoltage){
current = (voltage - centerVoltage) / (3.3 - centerVoltage) * ratedCurrent;
}else {
current = 0;
}
ESP_LOGI(TAG, "read sensor adc=%d: voltage=%.3fV, centerVoltage=%.3fV => current=%.3fA", (int)adcChannel, voltage, centerVoltage, current);
return current;
}
//===============================
//===== calibrateZeroAmpere =====
//===============================
void currentSensor::calibrateZeroAmpere(void){
//measure voltage
float prev = centerVoltage;
centerVoltage = getVoltage(adcChannel, 100);
ESP_LOGW(TAG, "defined centerVoltage (0A) to %.3f (previous %.3f)", centerVoltage, prev);
}

View File

@@ -0,0 +1,20 @@
#include <driver/adc.h>
//supported current sensor working method:
//0V = -ratedCurrent
//centerVoltage = 0A
//3.3V = ratedCurrent
class currentSensor{
public:
currentSensor (adc1_channel_t adcChannel_f, float ratedCurrent);
void calibrateZeroAmpere(void); //set current voltage to voltage representing 0A
float read(void); //get current ampere
private:
adc1_channel_t adcChannel;
float ratedCurrent;
uint32_t measure;
float voltage;
float current;
float centerVoltage = 3.3/2;
};

View File

@@ -0,0 +1,82 @@
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
}
#include "fan.hpp"
//tag for logging
static const char * TAG = "fan-control";
//-----------------------------
//-------- constructor --------
//-----------------------------
controlledFan::controlledFan (fan_config_t config_f, controlledMotor* motor1_f, controlledMotor* motor2_f ){
//copy config
config = config_f;
//copy pointer to motor objects
motor1 = motor1_f;
motor2 = motor2_f;
//initialize gpio pin
gpio_pad_select_gpio(config.gpio_fan);
gpio_set_direction(config.gpio_fan, GPIO_MODE_OUTPUT);
}
//--------------------------
//--------- handle ---------
//--------------------------
void controlledFan::handle(){
//get current state of the motor (motorctl.cpp)
motor1Status = motor1->getStatus();
motor2Status = motor2->getStatus();
//--- handle duty threshold ---
//update timestamp if any threshold exceeded
if (motor1Status.duty > config.dutyThreshold
|| motor2Status.duty > config.dutyThreshold){ //TODO add temperature threshold
if (!needsCooling){
timestamp_needsCoolingSet = esp_log_timestamp();
needsCooling = true;
}
timestamp_lastThreshold = esp_log_timestamp();
} else {
needsCooling = false;
}
//--- turn off condition ---
if (fanRunning
&& !needsCooling //no more cooling required
&& (motor1Status.duty == 0) && (motor2Status.duty == 0) //both motors are off
//-> keeps fans running even when lower than threshold already, however turnOffDelay already started TODO: start turn off delay after motor stop only?
&& (esp_log_timestamp() - timestamp_lastThreshold) > config.turnOffDelayMs){ //turn off delay passed
fanRunning = false;
gpio_set_level(config.gpio_fan, 0);
timestamp_turnedOff = esp_log_timestamp();
ESP_LOGI(TAG, "turned fan OFF gpio=%d, minOnMs=%d, WasOnMs=%d", (int)config.gpio_fan, config.minOnMs, esp_log_timestamp()-timestamp_turnedOn);
}
//--- turn on condition ---
if (!fanRunning
&& needsCooling
&& ((esp_log_timestamp() - timestamp_turnedOff) > config.minOffMs) //fans off long enough
&& ((esp_log_timestamp() - timestamp_needsCoolingSet) > config.minOnMs)){ //motors on long enough
fanRunning = true;
gpio_set_level(config.gpio_fan, 1);
timestamp_turnedOn = esp_log_timestamp();
ESP_LOGI(TAG, "turned fan ON gpio=%d, minOffMs=%d, WasOffMs=%d", (int)config.gpio_fan, config.minOffMs, esp_log_timestamp()-timestamp_turnedOff);
}
//TODO Add statemachine for more specific control? Exponential average?
//TODO idea: try other aproach? increment a variable with certain weights e.g. integrate over duty, then turn fans on and decrement the variable again
ESP_LOGD(TAG, "fanState=%d, duty1=%f, duty2=%f, needsCooling=%d", fanRunning, motor1Status.duty, motor2Status.duty, needsCooling);
}

View File

@@ -0,0 +1,49 @@
#pragma once
extern "C"
{
#include "driver/gpio.h"
}
#include "motorctl.hpp"
//--- fan_config_t ---
//struct with all config parameters for a fan
typedef struct fan_config_t {
gpio_num_t gpio_fan;
float dutyThreshold;
uint32_t minOnMs;
uint32_t minOffMs;
uint32_t turnOffDelayMs;
} fan_config;
//==================================
//====== controlledFan class =======
//==================================
class controlledFan {
public:
//--- constructor ---
controlledFan (fan_config_t config_f, controlledMotor* motor1_f, controlledMotor* motor2_f );
//--- functions ---
void handle(); //has to be run repeatedly in a slow loop
private:
//--- variables ---
bool fanRunning = false;
bool needsCooling = false;
uint32_t timestamp_needsCoolingSet;
uint32_t timestamp_lastThreshold = 0;
uint32_t timestamp_turnedOn = 0;
uint32_t timestamp_turnedOff = 0;
fan_config_t config;
controlledMotor * motor1;
controlledMotor * motor2;
motorCommand_t motor1Status;
motorCommand_t motor2Status;
};

View File

@@ -0,0 +1,270 @@
extern "C"
{
#include <stdio.h>
#include "mdns.h"
#include "cJSON.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 "config.hpp"
//tag for logging
static const char * TAG = "http";
static httpd_handle_t server = NULL;
//==============================
//===== start mdns service =====
//==============================
//TODO: test this, not working?
//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;
}
//==============================
//===== httpJoystick class =====
//==============================
//-----------------------
//----- constructor -----
//-----------------------
httpJoystick::httpJoystick( httpJoystick_config_t config_f ){
//copy config struct
config = config_f;
}
//--------------------------
//---- 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 ---
//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");
//--- 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);
//log received and parsed values
ESP_LOGI(TAG, "received values: x=%.3f y=%.3f",
data.x, data.y);
// 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);
//log processed values
ESP_LOGI(TAG, "processed values: x=%.3f y=%.3f radius=%.3f angle=%.3f pos=%s",
data.x, data.y, data.radius, data.angle, joystickPosStr[(int)data.position]);
//--- free memory ---
cJSON_Delete(payload);
//--- send data to control task via queue ---
//xQueueSend( joystickDataQueue, ( void * )&data, ( TickType_t ) 0 );
//changed to length = 1 -> overwrite - older values are no longer relevant
xQueueOverwrite( joystickDataQueue, ( void * )&data );
//--- return http response ---
httpd_resp_set_status(req, "204 NO CONTENT");
httpd_resp_send(req, NULL, 0);
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);
}
//============================
//===== 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;
joystick_url.uri = "/api/joystick";
joystick_url.method = HTTP_POST;
joystick_url.handler = on_joystick_url;
httpd_register_uri_handler(server, &joystick_url);
httpd_uri_t default_url;
default_url.uri = "/*";
default_url.method = HTTP_GET;
default_url.handler = on_default_url;
httpd_register_uri_handler(server, &default_url);
//previous approach with sockets:
// 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);
}

View File

@@ -0,0 +1,70 @@
#pragma once
extern "C"
{
#include "esp_http_server.h"
}
#include "joystick.hpp"
//============================
//===== 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();
//==============================
//===== 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

@@ -0,0 +1,572 @@
extern "C" {
#include "hal/timer_types.h"
}
#include "joystick.hpp"
//definition of string array to be able to convert state enum to readable string
const char* joystickPosStr[7] = {"CENTER", "Y_AXIS", "X_AXIS", "TOP_RIGHT", "TOP_LEFT", "BOTTOM_LEFT", "BOTTOM_RIGHT"};
//tags for logging
static const char * TAG = "evaluatedJoystick";
static const char * TAG_CMD = "joystickCommands";
//-----------------------------
//-------- constructor --------
//-----------------------------
//copy provided struct with all configuration and run init function
evaluatedJoystick::evaluatedJoystick(joystick_config_t config_f){
config = config_f;
init();
}
//----------------------------
//---------- init ------------
//----------------------------
void evaluatedJoystick::init(){
ESP_LOGI(TAG, "initializing joystick");
//initialize adc
adc1_config_width(ADC_WIDTH_BIT_12); //=> max resolution 4096
//FIXME: the following two commands each throw error
//"ADC: adc1_lock_release(419): adc1 lock release called before acquire"
//note: also happens for each get_raw for first call of readAdc function
//when run in main function that does not happen -> move init from constructor to be called in main
adc1_config_channel_atten(config.adc_x, ADC_ATTEN_DB_11); //max voltage
adc1_config_channel_atten(config.adc_y, ADC_ATTEN_DB_11); //max voltage
//define joystick center from current position
defineCenter(); //define joystick center from current position
}
//-----------------------------
//--------- readAdc -----------
//-----------------------------
//function for multisampling an anlog input
int evaluatedJoystick::readAdc(adc1_channel_t adc_channel, bool inverted) {
//make multiple measurements
int adc_reading = 0;
for (int i = 0; i < 16; i++) {
adc_reading += adc1_get_raw(adc_channel);
ets_delay_us(50);
}
adc_reading = adc_reading / 16;
//return original or inverted result
if (inverted) {
return 4095 - adc_reading;
} else {
return adc_reading;
}
}
//-------------------------------
//---------- getData ------------
//-------------------------------
//function that reads the joystick, calculates values and returns a struct with current data
joystickData_t evaluatedJoystick::getData() {
//get coordinates
//TODO individual tolerances for each axis? Otherwise some parameters can be removed
//TODO duplicate code for each axis below:
ESP_LOGV(TAG, "getting X coodrdinate...");
uint32_t adcRead;
adcRead = readAdc(config.adc_x, config.x_inverted);
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;
ESP_LOGD(TAG, "X: adc-raw=%d \tadc-conv=%d \tmin=%d \t max=%d \tcenter=%d \tinverted=%d => x=%.3f",
adc1_get_raw(config.adc_x), adcRead, config.x_min, config.x_max, x_center, config.x_inverted, x);
ESP_LOGV(TAG, "getting Y coodrinate...");
adcRead = readAdc(config.adc_y, config.y_inverted);
float y = scaleCoordinate(adcRead, config.y_min, config.y_max, y_center, config.tolerance_zeroY_per, config.tolerance_end_per);
data.y = y;
ESP_LOGD(TAG, "Y: adc-raw=%d \tadc-conv=%d \tmin=%d \t max=%d \tcenter=%d \tinverted=%d => y=%.3lf",
adc1_get_raw(config.adc_y), adcRead, config.y_min, config.y_max, y_center, config.y_inverted, y);
//calculate radius
data.radius = sqrt(pow(data.x,2) + pow(data.y,2));
if (data.radius > 1-config.tolerance_radius) {
data.radius = 1;
}
//calculate angle
data.angle = (atan(data.y/data.x) * 180) / 3.141;
//define position
data.position = joystick_evaluatePosition(x, y);
ESP_LOGD(TAG, "X=%.2f Y=%.2f radius=%.2f angle=%.2f", data.x, data.y, data.radius, data.angle);
return data;
}
//----------------------------
//------ defineCenter --------
//----------------------------
//function that defines the current position of the joystick as center position
void evaluatedJoystick::defineCenter(){
//read voltage from adc
x_center = readAdc(config.adc_x, config.x_inverted);
y_center = readAdc(config.adc_y, config.y_inverted);
ESP_LOGW(TAG, "defined center to x=%d, y=%d", x_center, y_center);
}
//==============================
//====== 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, "scaling: in=%.3f coordinate=%.3f, tolZero=%.3f, tolEnd=%.3f", input, coordinate, tolerance_zero, tolerance_end);
//return coordinate (-1 to 1)
return coordinate;
}
//===========================================
//====== joystick_scaleCoordinatesExp =======
//===========================================
//local function that scales the absolute value of a variable exponentionally
float scaleExp(float value, float exponent){
float result = powf(fabs(value), exponent);
if (value >= 0) {
return result;
} else {
return -result;
}
}
//function that updates a joystickData object with exponentionally scaling applied to coordinates
void joystick_scaleCoordinatesExp(joystickData_t * data, float exponent){
//scale x and y coordinate
data->x = scaleExp(data->x, exponent);
data->y = scaleExp(data->y, exponent);
//re-calculate radius
data->radius = sqrt(pow(data->x,2) + pow(data->y,2));
if (data->radius > 1-0.07) {//FIXME hardcoded radius tolerance
data->radius = 1;
}
}
//==============================================
//====== joystick_scaleCoordinatesLinear =======
//==============================================
//local function that scales value from -1-1 to -1-1 with two different slopes before and after a specified point
//slope1: for value from 0 to pointX -> scale linear from 0 to pointY
//slope2: for value from pointX to 1 -> scale linear from pointY to 1
float scaleLinPoint(float value, float pointX, float pointY){
float result;
if (fabs(value) <= pointX) {
//--- scale on line from 0 to point ---
result = fabs(value) * (pointY/pointX);
} else {
//--- scale on line from point to 1 ---
float m = (1-pointY) / (1-pointX);
result = fabs(value) * m + (1 - m);
}
//--- return result with same sign as input ---
if (value >= 0) {
return result;
} else {
return -result;
}
}
//function that updates a joystickData object with linear scaling applied to coordinates
//e.g. use to use more joystick resolution for lower speeds
//TODO rename this function to more general name (scales not only coordinates e.g. adjusts radius, in future angle...)
void joystick_scaleCoordinatesLinear(joystickData_t * data, float pointX, float pointY){
// --- scale x and y coordinate --- DISABLED
/*
data->x = scaleLinPoint(data->x, pointX, pointY);
data->y = scaleLinPoint(data->y, pointX, pointY);
//re-calculate radius
data->radius = sqrt(pow(data->x,2) + pow(data->y,2));
if (data->radius > 1-0.1) {//FIXME hardcoded radius tolerance
data->radius = 1;
}
*/
//note: issue with scaling X, Y coordinates:
// - messed up radius calculation - radius never gets 1 at diagonal positions
//==> only scaling radius as only speed should be more acurate at low radius:
//TODO make that clear and rename function, since it does not scale coordinates - just radius
//--- scale radius ---
data-> radius = scaleLinPoint(data->radius, pointX, pointY);
}
//=============================================
//========= joystick_evaluatePosition =========
//=============================================
//function that defines and returns enum joystickPos from x and y coordinates
joystickPos_t joystick_evaluatePosition(float x, float y){
//define position
//--- center ---
if((fabs(x) == 0) && (fabs(y) == 0)){
return joystickPos_t::CENTER;
}
//--- x axis ---
else if(fabs(y) == 0){
return joystickPos_t::X_AXIS;
}
//--- y axis ---
else if(fabs(x) == 0){
return joystickPos_t::Y_AXIS;
}
//--- top right ---
else if(x > 0 && y > 0){
return joystickPos_t::TOP_RIGHT;
}
//--- top left ---
else if(x < 0 && y > 0){
return joystickPos_t::TOP_LEFT;
}
//--- bottom left ---
else if(x < 0 && y < 0){
return joystickPos_t::BOTTOM_LEFT;
}
//--- bottom right ---
else if(x > 0 && y < 0){
return joystickPos_t::BOTTOM_RIGHT;
}
//--- other ---
else {
return joystickPos_t::CENTER;
}
}
//============================================
//========= joystick_CommandsDriving =========
//============================================
//function that generates commands for both motors from the joystick data
motorCommands_t joystick_generateCommandsDriving(joystickData_t data, bool altStickMapping){
//struct with current data of the joystick
//typedef struct joystickData_t {
// joystickPos_t position;
// float x;
// float y;
// float radius;
// float angle;
//} joystickData_t;
//--- variables ---
motorCommands_t commands;
float dutyMax = 90; //TODO add this to config, make changeable during runtime
float dutyOffset = 5; //immediately starts with this duty, TODO add this to config
float dutyRange = dutyMax - dutyOffset;
float ratio = fabs(data.angle) / 90; //90degree = x=0 || 0degree = y=0
//--- snap ratio to max at angle threshold ---
//(-> more joystick area where inner wheel is off when turning)
/*
//FIXME works, but armchair unsusable because of current bug with motor driver (inner motor freezes after turn)
float ratioClipThreshold = 0.3;
if (ratio < ratioClipThreshold) ratio = 0;
else if (ratio > 1-ratioClipThreshold) ratio = 1;
//TODO subtract this clip threshold from available joystick range at ratio usage
*/
//--- experimental alternative control mode ---
if (altStickMapping == true){
//swap BOTTOM_LEFT and BOTTOM_RIGHT
if (data.position == joystickPos_t::BOTTOM_LEFT){
data.position = joystickPos_t::BOTTOM_RIGHT;
}
else if (data.position == joystickPos_t::BOTTOM_RIGHT){
data.position = joystickPos_t::BOTTOM_LEFT;
}
}
//--- handle all positions ---
//define target direction and duty according to position
switch (data.position){
case joystickPos_t::CENTER:
commands.left.state = motorstate_t::IDLE;
commands.right.state = motorstate_t::IDLE;
commands.left.duty = 0;
commands.right.duty = 0;
break;
case joystickPos_t::Y_AXIS:
if (data.y > 0){
commands.left.state = motorstate_t::FWD;
commands.right.state = motorstate_t::FWD;
} else {
commands.left.state = motorstate_t::REV;
commands.right.state = motorstate_t::REV;
}
commands.left.duty = fabs(data.y) * dutyRange + dutyOffset;
commands.right.duty = commands.left.duty;
break;
case joystickPos_t::X_AXIS:
if (data.x > 0) {
commands.left.state = motorstate_t::FWD;
commands.right.state = motorstate_t::REV;
} else {
commands.left.state = motorstate_t::REV;
commands.right.state = motorstate_t::FWD;
}
commands.left.duty = fabs(data.x) * dutyRange + dutyOffset;
commands.right.duty = commands.left.duty;
break;
case joystickPos_t::TOP_RIGHT:
commands.left.state = motorstate_t::FWD;
commands.right.state = motorstate_t::FWD;
commands.left.duty = data.radius * dutyRange + dutyOffset;
commands.right.duty = data.radius * dutyRange - (data.radius*dutyRange + dutyOffset)*(1-ratio) + dutyOffset;
break;
case joystickPos_t::TOP_LEFT:
commands.left.state = motorstate_t::FWD;
commands.right.state = motorstate_t::FWD;
commands.left.duty = data.radius * dutyRange - (data.radius*dutyRange + dutyOffset)*(1-ratio) + dutyOffset;
commands.right.duty = data.radius * dutyRange + dutyOffset;
break;
case joystickPos_t::BOTTOM_LEFT:
commands.left.state = motorstate_t::REV;
commands.right.state = motorstate_t::REV;
commands.left.duty = data.radius * dutyRange + dutyOffset;
commands.right.duty = data.radius * dutyRange - (data.radius*dutyRange + dutyOffset)*(1-ratio) + dutyOffset;
break;
case joystickPos_t::BOTTOM_RIGHT:
commands.left.state = motorstate_t::REV;
commands.right.state = motorstate_t::REV;
commands.left.duty = data.radius * dutyRange - (data.radius*dutyRange + dutyOffset)*(1-ratio) + dutyOffset;
commands.right.duty = data.radius * dutyRange + dutyOffset;
break;
}
ESP_LOGI(TAG_CMD, "generated commands from data: state=%s, angle=%.3f, ratio=%.3f/%.3f, radius=%.2f, x=%.2f, y=%.2f",
joystickPosStr[(int)data.position], data.angle, ratio, (1-ratio), data.radius, data.x, data.y);
ESP_LOGI(TAG_CMD, "motor left: state=%s, duty=%.3f", motorstateStr[(int)commands.left.state], commands.left.duty);
ESP_LOGI(TAG_CMD, "motor right: state=%s, duty=%.3f", motorstateStr[(int)commands.right.state], commands.right.duty);
return commands;
}
//============================================
//========= joystick_CommandsShaking =========
//============================================
//--- variable declarations ---
uint32_t shake_timestamp_turnedOn = 0;
uint32_t shake_timestamp_turnedOff = 0;
bool shake_state = false;
joystickPos_t lastStickPos = joystickPos_t::CENTER;
//stick position quadrant only with "X_AXIS and Y_AXIS" as hysteresis
joystickPos_t stickQuadrant = joystickPos_t::CENTER;
//--- configure shake mode --- TODO: move this to config
uint32_t shake_msOffMax = 80;
uint32_t shake_msOnMax = 120;
float dutyShake = 60;
//function that generates commands for both motors from the joystick data
motorCommands_t joystick_generateCommandsShaking(joystickData_t data){
//--- handle pulsing shake variable ---
//TODO remove this, make individual per mode?
//TODO only run this when not CENTER anyways?
motorCommands_t commands;
float ratio = fabs(data.angle) / 90; //90degree = x=0 || 0degree = y=0
//calculate on/off duration
uint32_t msOn = shake_msOnMax * data.radius;
uint32_t msOff = shake_msOffMax * data.radius;
//evaluate state (on/off)
if (data.radius > 0 ){
//currently off
if (shake_state == false){
//off long enough
if (esp_log_timestamp() - shake_timestamp_turnedOff > msOff) {
//turn on
shake_state = true;
shake_timestamp_turnedOn = esp_log_timestamp();
}
}
//currently on
else {
//on long enough
if (esp_log_timestamp() - shake_timestamp_turnedOn > msOn) {
//turn off
shake_state = false;
shake_timestamp_turnedOff = esp_log_timestamp();
}
}
}
//joystick is at center
else {
shake_state = false;
shake_timestamp_turnedOff = esp_log_timestamp();
}
//struct with current data of the joystick
//typedef struct joystickData_t {
// joystickPos_t position;
// float x;
// float y;
// float radius;
// float angle;
//} joystickData_t;
//--- evaluate stick position ---
//4 quadrants and center only - with X and Y axis as hysteresis
switch (data.position){
case joystickPos_t::CENTER:
//immediately set to center at center
stickQuadrant = joystickPos_t::CENTER;
break;
case joystickPos_t::Y_AXIS:
//when moving from center to axis initially start in a certain quadrant
if (stickQuadrant == joystickPos_t::CENTER) {
if (data.y > 0){
stickQuadrant = joystickPos_t::TOP_RIGHT;
} else {
stickQuadrant = joystickPos_t::BOTTOM_RIGHT;
}
}
break;
case joystickPos_t::X_AXIS:
//when moving from center to axis initially start in a certain quadrant
if (stickQuadrant == joystickPos_t::CENTER) {
if (data.x > 0){
stickQuadrant = joystickPos_t::TOP_RIGHT;
} else {
stickQuadrant = joystickPos_t::TOP_LEFT;
}
}
break;
case joystickPos_t::TOP_RIGHT:
case joystickPos_t::TOP_LEFT:
case joystickPos_t::BOTTOM_LEFT:
case joystickPos_t::BOTTOM_RIGHT:
//update/change evaluated pos when in one of the 4 quadrants
stickQuadrant = data.position;
//TODO: maybe beep when switching mode? (difficult because beep object has to be passed to function)
break;
}
//--- handle different modes (joystick in any of 4 quadrants) ---
switch (stickQuadrant){
case joystickPos_t::CENTER:
case joystickPos_t::X_AXIS: //never true
case joystickPos_t::Y_AXIS: //never true
commands.left.state = motorstate_t::IDLE;
commands.right.state = motorstate_t::IDLE;
commands.left.duty = 0;
commands.right.duty = 0;
ESP_LOGI(TAG_CMD, "generate shake commands: CENTER -> idle");
return commands;
break;
//4 different modes
case joystickPos_t::TOP_RIGHT:
commands.left.state = motorstate_t::FWD;
commands.right.state = motorstate_t::FWD;
break;
case joystickPos_t::TOP_LEFT:
commands.left.state = motorstate_t::REV;
commands.right.state = motorstate_t::REV;
break;
case joystickPos_t::BOTTOM_LEFT:
commands.left.state = motorstate_t::REV;
commands.right.state = motorstate_t::FWD;
break;
case joystickPos_t::BOTTOM_RIGHT:
commands.left.state = motorstate_t::FWD;
commands.right.state = motorstate_t::REV;
break;
}
//--- turn motors on/off depending on pulsing shake variable ---
if (shake_state == true){
//set duty to shake
commands.left.duty = dutyShake;
commands.right.duty = dutyShake;
//directions are defined above depending on mode
} else {
commands.left.state = motorstate_t::IDLE;
commands.right.state = motorstate_t::IDLE;
commands.left.duty = 0;
commands.right.duty = 0;
}
ESP_LOGI(TAG_CMD, "generated commands from data: state=%s, angle=%.3f, ratio=%.3f/%.3f, radius=%.2f, x=%.2f, y=%.2f",
joystickPosStr[(int)data.position], data.angle, ratio, (1-ratio), data.radius, data.x, data.y);
ESP_LOGI(TAG_CMD, "motor left: state=%s, duty=%.3f", motorstateStr[(int)commands.left.state], commands.left.duty);
ESP_LOGI(TAG_CMD, "motor right: state=%s, duty=%.3f", motorstateStr[(int)commands.right.state], commands.right.duty);
return commands;
}

View File

@@ -0,0 +1,153 @@
#pragma once
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_log.h"
#include "esp_err.h"
}
#include <cmath>
#include "motorctl.hpp" //for declaration of motorCommands_t struct
//======================================
//========= evaluated Joystick =========
//======================================
//class which evaluates a joystick with 2 analog signals
// - scales the adc input to coordinates with detailed tolerances
// - calculates angle and radius
// - defines an enum with position information
//--------------------------------------------
//---- struct, enum, variable declarations ---
//--------------------------------------------
//struct with all required configuration parameters
typedef struct joystick_config_t {
//analog inputs the axis are connected
adc1_channel_t adc_x;
adc1_channel_t adc_y;
//percentage of joystick range the coordinate of the axis snaps to 0 (0-100)
int tolerance_zeroX_per;
int tolerance_zeroY_per;
//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)
float tolerance_radius;
//min and max adc values of each axis
int x_min;
int x_max;
int y_min;
int y_max;
//invert adc measurement (e.g. when moving joystick up results in a decreasing voltage)
bool x_inverted;
bool y_inverted;
} joystick_config_t;
//enum for describing the position of the joystick
enum class joystickPos_t {CENTER, Y_AXIS, X_AXIS, TOP_RIGHT, TOP_LEFT, BOTTOM_LEFT, BOTTOM_RIGHT};
extern const char* joystickPosStr[7];
//struct with current data of the joystick
typedef struct joystickData_t {
joystickPos_t position;
float x;
float y;
float radius;
float angle;
} joystickData_t;
//------------------------------------
//----- evaluatedJoystick class -----
//------------------------------------
class evaluatedJoystick {
public:
//--- constructor ---
evaluatedJoystick(joystick_config_t config_f);
//--- functions ---
joystickData_t getData(); //read joystick, calculate values and return the data in a struct
void defineCenter(); //define joystick center from current position
private:
//--- functions ---
//initialize adc inputs, define center
void init();
//read adc while making multiple samples with option to invert the result
int readAdc(adc1_channel_t adc_channel, bool inverted = false);
//--- variables ---
joystick_config_t config;
int x_center;
int y_center;
joystickData_t data;
float x;
float y;
};
//============================================
//========= joystick_CommandsDriving =========
//============================================
//function that generates commands for both motors from the joystick data
//motorCommands_t joystick_generateCommandsDriving(evaluatedJoystick joystick);
motorCommands_t joystick_generateCommandsDriving(joystickData_t data, bool altStickMapping = false);
//============================================
//========= joystick_CommandsShaking =========
//============================================
//function that generates commands for both motors from the joystick data
//motorCommands_t joystick_generateCommandsDriving(evaluatedJoystick joystick);
motorCommands_t joystick_generateCommandsShaking(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_scaleCoordinatesExp =======
//===========================================
//function that updates a joystickData object with exponentionally scaling applied to coordinates
//e.g. use to use more joystick resolution for lower speeds
void joystick_scaleCoordinatesExp(joystickData_t * data, float exponent);
//==============================================
//====== joystick_scaleCoordinatesLinear =======
//==============================================
//function that updates a joystickData object with linear scaling applied to coordinates
//scales coordinates with two different slopes before and after a specified point
//slope1: for value from 0 to pointX -> scale linear from 0 to pointY
//slope2: for value from pointX to 1 -> scale linear from pointY to 1
//=> best to draw the lines and point in a graph
//e.g. use to use more joystick resolution for lower speeds
void joystick_scaleCoordinatesLinear(joystickData_t * data, float pointX, float pointY);
//=============================================
//========= joystick_evaluatePosition =========
//=============================================
//function that defines and returns enum joystickPos from x and y coordinates
joystickPos_t joystick_evaluatePosition(float x, float y);

View File

@@ -0,0 +1,299 @@
extern "C"
{
#include <stdio.h>
#include <esp_system.h>
#include <esp_event.h>
#include <nvs_flash.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "esp_spiffs.h"
#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";
//====================================
//========== motorctl task ===========
//====================================
//task for handling the motors (ramp, current limit, driver)
void task_motorctl( void * pvParameters ){
ESP_LOGI(TAG, "starting handle loop...");
while(1){
motorRight.handle();
motorLeft.handle();
//10khz -> T=100us
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
//======================================
//============ buzzer task =============
//======================================
//TODO: move the task creation to buzzer class (buzzer.cpp)
//e.g. only have function buzzer.createTask() in app_main
void task_buzzer( void * pvParameters ){
ESP_LOGI("task_buzzer", "Start of buzzer task...");
//run function that waits for a beep events to arrive in the queue
//and processes them
buzzer.processQueue();
}
//=======================================
//============ control task =============
//=======================================
//task that controls the armchair modes and initiates commands generation and applies them to driver
void task_control( void * pvParameters ){
ESP_LOGI(TAG, "Initializing controlledArmchair and starting handle loop");
//start handle loop (control object declared in config.hpp)
control.startHandleLoop();
}
//======================================
//============ button task =============
//======================================
//task that handles the button interface/commands
void task_button( void * pvParameters ){
ESP_LOGI(TAG, "Initializing command-button and starting handle loop");
//create button instance
buttonCommands commandButton(&buttonJoystick, &joystick, &control, &buzzer, &motorLeft, &motorRight);
//start handle loop
commandButton.startHandleLoop();
}
//=======================================
//============== fan task ===============
//=======================================
//task that controlls fans for cooling the drivers
void task_fans( void * pvParameters ){
ESP_LOGI(TAG, "Initializing fans and starting fan handle loop");
//create fan instances with config defined in config.cpp
controlledFan fan(configCooling, &motorLeft, &motorRight);
//repeatedly run fan handle function in a slow loop
while(1){
fan.handle();
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
//=================================
//========== init spiffs ==========
//=================================
//initialize spi flash filesystem (used for webserver)
void init_spiffs(){
ESP_LOGI(TAG, "init spiffs");
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);
size_t total = 0;
size_t used = 0;
esp_spiffs_info(NULL, &total, &used);
ESP_LOGI(TAG, "SPIFFS: total %d, used %d", total, used);
esp_vfs_spiffs_unregister(NULL);
}
//==================================
//======== define loglevels ========
//==================================
void setLoglevels(void){
//set loglevel for all tags:
esp_log_level_set("*", ESP_LOG_WARN);
//--- 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_INFO);
//esp_log_level_set("motor-control", ESP_LOG_DEBUG);
//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("fan-control", ESP_LOG_INFO);
esp_log_level_set("wifi", ESP_LOG_INFO);
esp_log_level_set("http", ESP_LOG_INFO);
esp_log_level_set("automatedArmchair", ESP_LOG_DEBUG);
//esp_log_level_set("current-sensors", ESP_LOG_INFO);
}
//=================================
//=========== app_main ============
//=================================
extern "C" void app_main(void) {
//enable 5V volate regulator
gpio_pad_select_gpio(GPIO_NUM_17);
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_17, 1);
//---- define log levels ----
setLoglevels();
//----------------------------------------------
//--- create task for controlling the motors ---
//----------------------------------------------
//task that receives commands, handles ramp and current limit and executes commands using the motordriver function
xTaskCreate(&task_motorctl, "task_motor-control", 2048, NULL, 6, NULL);
//------------------------------
//--- create task for buzzer ---
//------------------------------
xTaskCreate(&task_buzzer, "task_buzzer", 2048, NULL, 2, NULL);
//-------------------------------
//--- create task for control ---
//-------------------------------
//task that generates motor commands depending on the current mode and sends those to motorctl task
xTaskCreate(&task_control, "task_control", 4096, NULL, 5, NULL);
//------------------------------
//--- create task for button ---
//------------------------------
//task that evaluates and processes the button input and runs the configured commands
xTaskCreate(&task_button, "task_button", 4096, NULL, 4, NULL);
//-----------------------------------
//--- create task for fan control ---
//-----------------------------------
//task that evaluates and processes the button input and runs the configured commands
xTaskCreate(&task_fans, "task_fans", 2048, NULL, 1, NULL);
//beep at startup
buzzer.beep(3, 70, 50);
//--- initialize nvs-flash and netif (needed for wifi) ---
wifi_initNvs_initNetif();
//--- initialize spiffs ---
init_spiffs();
//--- 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);
//--- main loop ---
//does nothing except for testing things
while(1){
vTaskDelay(1000 / portTICK_PERIOD_MS);
//---------------------------------
//-------- TESTING section --------
//---------------------------------
// //--- test 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);
//--- test 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);
//--- test button ---
//buttonJoystick.handle();
// if (buttonJoystick.risingEdge){
// ESP_LOGI(TAG, "button pressed, was released for %d ms", buttonJoystick.msReleased);
// buzzer.beep(2, 100, 50);
// }else if (buttonJoystick.fallingEdge){
// ESP_LOGI(TAG, "button released, was pressed for %d ms", buttonJoystick.msPressed);
// buzzer.beep(1, 200, 0);
// }
//--- test joystick commands ---
// motorCommands_t commands = joystick_generateCommandsDriving(joystick);
// motorRight.setTarget(commands.right.state, commands.right.duty); //TODO make motorctl.setTarget also accept motorcommand struct directly
// motorLeft.setTarget(commands.left.state, commands.left.duty); //TODO make motorctl.setTarget also accept motorcommand struct directly
// //motorRight.setTarget(commands.right.state, commands.right.duty);
//--- test joystick class ---
//joystickData_t data = joystick.getData();
//ESP_LOGI(TAG, "position=%s, x=%.1f%%, y=%.1f%%, radius=%.1f%%, angle=%.2f",
// joystickPosStr[(int)data.position], data.x*100, data.y*100, data.radius*100, data.angle);
//--- test the motor driver ---
//fade up duty - forward
// for (int duty=0; duty<=100; duty+=5) {
// motorLeft.setTarget(motorstate_t::FWD, duty);
// vTaskDelay(100 / portTICK_PERIOD_MS);
// }
//--- test controlledMotor --- (ramp)
// //brake for 1 s
// motorLeft.setTarget(motorstate_t::BRAKE);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
// //command 90% - reverse
// motorLeft.setTarget(motorstate_t::REV, 90);
// vTaskDelay(5000 / portTICK_PERIOD_MS);
// //command 100% - forward
// motorLeft.setTarget(motorstate_t::FWD, 100);
// vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

View File

@@ -0,0 +1,330 @@
#include "motorctl.hpp"
//tag for logging
static const char * TAG = "motor-control";
//=============================
//======== constructor ========
//=============================
//constructor, simultaniously initialize instance of motor driver 'motor' and current sensor 'cSensor' with provided config (see below lines after ':')
controlledMotor::controlledMotor(single100a_config_t config_driver, motorctl_config_t config_control):
motor(config_driver),
cSensor(config_control.currentSensor_adc, config_control.currentSensor_ratedCurrent) {
//copy parameters for controlling the motor
config = config_control;
//copy configured default fading durations to actually used variables
msFadeAccel = config.msFadeAccel;
msFadeDecel = config.msFadeDecel;
init();
//TODO: add currentsensor object here
//currentSensor cSensor(config.currentSensor_adc, config.currentSensor_ratedCurrent);
}
//============================
//========== init ============
//============================
void controlledMotor::init(){
commandQueue = xQueueCreate( 1, sizeof( struct motorCommand_t ) );
//cSensor.calibrateZeroAmpere(); //currently done in currentsensor constructor TODO do this regularly e.g. in idle?
}
//----------------
//----- fade -----
//----------------
//local function that fades a variable
//- increments a variable (pointer) by given value
//- sets to target if already closer than increment
//TODO this needs testing
void fade(float * dutyNow, float dutyTarget, float dutyIncrement){
float dutyDelta = dutyTarget - *dutyNow;
if ( fabs(dutyDelta) > fabs(dutyIncrement) ) { //check if already close to target
*dutyNow = *dutyNow + dutyIncrement;
}
//already closer to target than increment
else {
*dutyNow = dutyTarget;
}
}
//----------------------------
//----- getStateFromDuty -----
//----------------------------
//local function that determines motor the direction from duty range -100 to 100
motorstate_t getStateFromDuty(float duty){
if(duty > 0) return motorstate_t::FWD;
if (duty < 0) return motorstate_t::REV;
return motorstate_t::IDLE;
}
//==============================
//=========== handle ===========
//==============================
//function that controls the motor driver and handles fading/ramp, current limit and deadtime
void controlledMotor::handle(){
//TODO: History: skip fading when motor was running fast recently / alternatively add rot-speed sensor
//--- receive commands from queue ---
if( xQueueReceive( commandQueue, &commandReceive, ( TickType_t ) 0 ) )
{
ESP_LOGD(TAG, "Read command from queue: state=%s, duty=%.2f", motorstateStr[(int)commandReceive.state], commandReceive.duty);
state = commandReceive.state;
dutyTarget = commandReceive.duty;
//--- convert duty ---
//define target duty (-100 to 100) from provided duty and motorstate
//this value is more suitable for the fading algorithm
switch(commandReceive.state){
case motorstate_t::BRAKE:
//update state
state = motorstate_t::BRAKE;
dutyTarget = 0;
break;
case motorstate_t::IDLE:
dutyTarget = 0;
break;
case motorstate_t::FWD:
dutyTarget = fabs(commandReceive.duty);
break;
case motorstate_t::REV:
dutyTarget = - fabs(commandReceive.duty);
break;
}
}
//--- calculate increment ---
//calculate increment for fading UP with passed time since last run and configured fade time
int64_t usPassed = esp_timer_get_time() - timestampLastRunUs;
if (msFadeAccel > 0){
dutyIncrementAccel = ( usPassed / ((float)msFadeAccel * 1000) ) * 100; //TODO define maximum increment - first run after startup (or long) pause can cause a very large increment
} else {
dutyIncrementAccel = 100;
}
//calculate increment for fading DOWN with passed time since last run and configured fade time
if (msFadeDecel > 0){
dutyIncrementDecel = ( usPassed / ((float)msFadeDecel * 1000) ) * 100;
} else {
dutyIncrementDecel = 100;
}
//--- BRAKE ---
//brake immediately, update state, duty and exit this cycle of handle function
if (state == motorstate_t::BRAKE){
motor.set(motorstate_t::BRAKE, 0);
dutyNow = 0;
return; //no need to run the fade algorithm
}
//--- calculate difference ---
dutyDelta = dutyTarget - dutyNow;
//positive: need to increase by that value
//negative: need to decrease
//----- FADING -----
//fade duty to target (up and down)
//TODO: this needs optimization (can be more clear and/or simpler)
if (dutyDelta > 0) { //difference positive -> increasing duty (-100 -> 100)
if (dutyNow < 0) { //reverse, decelerating
fade(&dutyNow, dutyTarget, dutyIncrementDecel);
}
else if (dutyNow >= 0) { //forward, accelerating
fade(&dutyNow, dutyTarget, dutyIncrementAccel);
}
}
else if (dutyDelta < 0) { //difference negative -> decreasing duty (100 -> -100)
if (dutyNow <= 0) { //reverse, accelerating
fade(&dutyNow, dutyTarget, - dutyIncrementAccel);
}
else if (dutyNow > 0) { //forward, decelerating
fade(&dutyNow, dutyTarget, - dutyIncrementDecel);
}
}
//----- CURRENT LIMIT -----
if ((config.currentLimitEnabled) && (dutyDelta != 0)){
currentNow = cSensor.read();
if (fabs(currentNow) > config.currentMax){
float dutyOld = dutyNow;
//adaptive decrement:
//Note current exceeded twice -> twice as much decrement: TODO: decrement calc needs finetuning, currently random values
dutyIncrementDecel = (currentNow/config.currentMax) * ( usPassed / ((float)msFadeDecel * 1500) ) * 100;
float currentLimitDecrement = ( (float)usPassed / ((float)1000 * 1000) ) * 100; //1000ms from 100 to 0
if (dutyNow < -currentLimitDecrement) {
dutyNow += currentLimitDecrement;
} else if (dutyNow > currentLimitDecrement) {
dutyNow -= currentLimitDecrement;
}
ESP_LOGW(TAG, "current limit exceeded! now=%.3fA max=%.1fA => decreased duty from %.3f to %.3f", currentNow, config.currentMax, dutyOld, dutyNow);
}
}
//--- define new motorstate --- (-100 to 100 => direction)
state=getStateFromDuty(dutyNow);
//--- DEAD TIME ----
//ensure minimum idle time between direction change to prevent driver overload
//FWD -> IDLE -> FWD continue without waiting
//FWD -> IDLE -> REV wait for dead-time in IDLE
//TODO check when changed only?
if ( //not enough time between last direction state
( state == motorstate_t::FWD && (esp_log_timestamp() - timestampsModeLastActive[(int)motorstate_t::REV] < config.deadTimeMs))
|| (state == motorstate_t::REV && (esp_log_timestamp() - timestampsModeLastActive[(int)motorstate_t::FWD] < config.deadTimeMs))
){
ESP_LOGD(TAG, "waiting dead-time... dir change %s -> %s", motorstateStr[(int)statePrev], motorstateStr[(int)state]);
if (!deadTimeWaiting){ //log start
deadTimeWaiting = true;
ESP_LOGW(TAG, "starting dead-time... %s -> %s", motorstateStr[(int)statePrev], motorstateStr[(int)state]);
}
//force IDLE state during wait
state = motorstate_t::IDLE;
dutyNow = 0;
} else {
if (deadTimeWaiting){ //log end
deadTimeWaiting = false;
ESP_LOGW(TAG, "dead-time ended - continue with %s", motorstateStr[(int)state]);
}
ESP_LOGV(TAG, "deadtime: no change below deadtime detected... dir=%s, duty=%.1f", motorstateStr[(int)state], dutyNow);
}
//--- save current actual motorstate and timestamp ---
//needed for deadtime
timestampsModeLastActive[(int)getStateFromDuty(dutyNow)] = esp_log_timestamp();
//(-100 to 100 => direction)
statePrev = getStateFromDuty(dutyNow);
//--- apply new target to motor ---
motor.set(state, fabs(dutyNow));
//note: BRAKE state is handled earlier
//--- update timestamp ---
timestampLastRunUs = esp_timer_get_time(); //update timestamp last run with current timestamp in microseconds
}
//===============================
//========== setTarget ==========
//===============================
//function to set the target mode and duty of a motor
//puts the provided command in a queue for the handle function running in another task
void controlledMotor::setTarget(motorstate_t state_f, float duty_f){
commandSend = {
.state = state_f,
.duty = duty_f
};
ESP_LOGD(TAG, "Inserted command to queue: state=%s, duty=%.2f", motorstateStr[(int)commandSend.state], commandSend.duty);
//send command to queue (overwrite if an old command is still in the queue and not processed)
xQueueOverwrite( commandQueue, ( void * )&commandSend);
//xQueueSend( commandQueue, ( void * )&commandSend, ( TickType_t ) 0 );
}
//===============================
//========== getStatus ==========
//===============================
//function which returns the current status of the motor in a motorCommand_t struct
motorCommand_t controlledMotor::getStatus(){
motorCommand_t status = {
.state = state,
.duty = dutyNow
};
//TODO: mutex
return status;
};
//===============================
//=========== setFade ===========
//===============================
//function for editing or enabling the fading/ramp of the motor control
//set/update fading duration/amount
void controlledMotor::setFade(fadeType_t fadeType, uint32_t msFadeNew){
//TODO: mutex for msFade variable also used in handle function
switch(fadeType){
case fadeType_t::ACCEL:
msFadeAccel = msFadeNew;
break;
case fadeType_t::DECEL:
msFadeDecel = msFadeNew;
break;
}
}
//enable (set to default value) or disable fading
void controlledMotor::setFade(fadeType_t fadeType, bool enabled){
uint32_t msFadeNew = 0; //define new fade time as default disabled
if(enabled){ //enable
//set to default/configured value
switch(fadeType){
case fadeType_t::ACCEL:
msFadeNew = config.msFadeAccel;
break;
case fadeType_t::DECEL:
msFadeNew = config.msFadeDecel;
break;
}
}
//apply new Fade value
setFade(fadeType, msFadeNew);
}
//==================================
//=========== toggleFade ===========
//==================================
//toggle fading between OFF and default value
bool controlledMotor::toggleFade(fadeType_t fadeType){
uint32_t msFadeNew = 0;
bool enabled = false;
switch(fadeType){
case fadeType_t::ACCEL:
if (msFadeAccel == 0){
msFadeNew = config.msFadeAccel;
enabled = true;
} else {
msFadeNew = 0;
}
break;
case fadeType_t::DECEL:
if (msFadeDecel == 0){
msFadeNew = config.msFadeAccel;
enabled = true;
} else {
msFadeNew = 0;
}
break;
}
//apply new Fade value
setFade(fadeType, msFadeNew);
//return new state (fading enabled/disabled)
return enabled;
}

View File

@@ -0,0 +1,104 @@
#pragma once
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_timer.h"
}
#include "motordrivers.hpp"
#include "currentsensor.hpp"
//=======================================
//====== struct/type declarations ======
//=======================================
//struct for sending command for one motor in the queue
struct motorCommand_t {
motorstate_t state;
float duty;
};
//struct containing commands for two motors
typedef struct motorCommands_t {
motorCommand_t left;
motorCommand_t right;
} motorCommands_t;
//struct with all config parameters for a motor regarding ramp and current limit
typedef struct motorctl_config_t {
uint32_t msFadeAccel; //acceleration of the motor (ms it takes from 0% to 100%)
uint32_t msFadeDecel; //deceleration of the motor (ms it takes from 100% to 0%)
bool currentLimitEnabled;
adc1_channel_t currentSensor_adc;
float currentSensor_ratedCurrent;
float currentMax;
uint32_t deadTimeMs; //time motor stays in IDLE before direction change
} motorctl_config_t;
//enum fade type (acceleration, deceleration)
//e.g. used for specifying which fading should be modified with setFade, togleFade functions
enum class fadeType_t {ACCEL, DECEL};
//===================================
//====== controlledMotor class ======
//===================================
class controlledMotor {
public:
//--- functions ---
controlledMotor(single100a_config_t config_driver, motorctl_config_t config_control); //constructor with structs for configuring motordriver and parameters for control TODO: add configuration for currentsensor
void handle(); //controls motor duty with fade and current limiting feature (has to be run frequently by another task)
void setTarget(motorstate_t state_f, float duty_f = 0); //adds target command to queue for handle function
motorCommand_t getStatus(); //get current status of the motor (returns struct with state and duty)
void setFade(fadeType_t fadeType, bool enabled); //enable/disable acceleration or deceleration fading
void setFade(fadeType_t fadeType, uint32_t msFadeNew); //set acceleration or deceleration fade time
bool toggleFade(fadeType_t fadeType); //toggle acceleration or deceleration on/off
//TODO set current limit method
private:
//--- functions ---
void init(); //creates currentsensor objects, motordriver objects and queue
//--- objects ---
//motor driver
single100a motor;
//queue for sending commands to the separate task running the handle() function very fast
QueueHandle_t commandQueue = NULL;
//current sensor
currentSensor cSensor;
//--- variables ---
//struct for storing control specific parameters
motorctl_config_t config;
motorstate_t state = motorstate_t::IDLE;
float currentMax;
float currentNow;
float dutyTarget;
float dutyNow;
float dutyIncrementAccel;
float dutyIncrementDecel;
float dutyDelta;
uint32_t msFadeAccel;
uint32_t msFadeDecel;
uint32_t ramp;
int64_t timestampLastRunUs;
bool deadTimeWaiting = false;
uint32_t timestampsModeLastActive[4] = {};
motorstate_t statePrev = motorstate_t::FWD;
struct motorCommand_t commandReceive = {};
struct motorCommand_t commandSend = {};
};

View File

@@ -0,0 +1,129 @@
#include "motordrivers.hpp"
//TODO: move from ledc to mcpwm?
//https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/api-reference/peripherals/mcpwm.html#
//https://github.com/espressif/esp-idf/tree/v4.3/examples/peripherals/mcpwm/mcpwm_basic_config
//Note fade functionality provided by LEDC would be very useful but unfortunately is not usable here because:
//"Due to hardware limitations, there is no way to stop a fade before it reaches its target duty."
//definition of string array to be able to convert state enum to readable string
const char* motorstateStr[4] = {"IDLE", "FWD", "REV", "BRAKE"};
//tag for logging
static const char * TAG = "motordriver";
//====================================
//===== single100a motor driver ======
//====================================
//-----------------------------
//-------- constructor --------
//-----------------------------
//copy provided struct with all configuration and run init function
single100a::single100a(single100a_config_t config_f){
config = config_f;
init();
}
//----------------------------
//---------- init ------------
//----------------------------
//function to initialize pwm output, gpio pins and calculate maxDuty
void single100a::init(){
//--- configure ledc timer ---
ledc_timer_config_t ledc_timer;
ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_timer.timer_num = config.ledc_timer;
ledc_timer.duty_resolution = config.resolution; //13bit gives max 5khz freq
ledc_timer.freq_hz = config.pwmFreq;
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
//apply configuration
ledc_timer_config(&ledc_timer);
//--- configure ledc channel ---
ledc_channel_config_t ledc_channel;
ledc_channel.channel = config.ledc_channel;
ledc_channel.duty = 0;
ledc_channel.gpio_num = config.gpio_pwm;
ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_channel.hpoint = 0;
ledc_channel.timer_sel = config.ledc_timer;
ledc_channel.intr_type = LEDC_INTR_DISABLE;
ledc_channel.flags.output_invert = 0; //TODO: add config option to invert the pwm output?
//apply configuration
ledc_channel_config(&ledc_channel);
//--- define gpio pins as outputs ---
gpio_pad_select_gpio(config.gpio_a);
gpio_set_direction(config.gpio_a, GPIO_MODE_OUTPUT);
gpio_pad_select_gpio(config.gpio_b);
gpio_set_direction(config.gpio_b, GPIO_MODE_OUTPUT);
//--- calculate max duty according to selected resolution ---
dutyMax = pow(2, ledc_timer.duty_resolution) -1;
ESP_LOGI(TAG, "initialized single100a driver");
ESP_LOGI(TAG, "resolution=%dbit, dutyMax value=%d, resolution=%.4f %%", ledc_timer.duty_resolution, dutyMax, 100/(float)dutyMax);
}
//---------------------------
//----------- set -----------
//---------------------------
//function to put the h-bridge module in the desired state and duty cycle
void single100a::set(motorstate_t state_f, float duty_f){
//scale provided target duty in percent to available resolution for ledc
uint32_t dutyScaled;
if (duty_f > 100) { //target duty above 100%
dutyScaled = dutyMax;
} else if (duty_f <= 0) { //target at or below 0%
state_f = motorstate_t::IDLE;
dutyScaled = 0;
} else { //target duty 0-100%
//scale duty to available resolution
dutyScaled = duty_f / 100 * dutyMax;
}
//put the single100a h-bridge module in the desired state update duty-cycle
switch (state_f){
case motorstate_t::IDLE:
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, dutyScaled);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
//TODO: to fix bugged state of h-bridge module when idle and start again, maybe try to leave pwm signal on for some time before updating a/b pins?
//no brake: (freewheel)
//gpio_set_level(config.gpio_a, config.aEnabledPinState);
//gpio_set_level(config.gpio_b, !config.bEnabledPinState);
gpio_set_level(config.gpio_a, config.aEnabledPinState);
gpio_set_level(config.gpio_b, config.bEnabledPinState);
break;
case motorstate_t::BRAKE:
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, 0);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
//brake:
gpio_set_level(config.gpio_a, !config.aEnabledPinState);
gpio_set_level(config.gpio_b, !config.bEnabledPinState);
break;
case motorstate_t::FWD:
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, dutyScaled);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
//forward:
gpio_set_level(config.gpio_a, config.aEnabledPinState);
gpio_set_level(config.gpio_b, !config.bEnabledPinState);
break;
case motorstate_t::REV:
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, dutyScaled);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
//reverse:
gpio_set_level(config.gpio_a, !config.aEnabledPinState);
gpio_set_level(config.gpio_b, config.bEnabledPinState);
break;
}
ESP_LOGD(TAG, "set module to state=%s, duty=%d/%d, duty_input=%.3f%%", motorstateStr[(int)state_f], dutyScaled, dutyMax, duty_f);
}

View File

@@ -0,0 +1,65 @@
#pragma once
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "driver/ledc.h"
#include "esp_err.h"
}
#include <cmath>
//====================================
//===== single100a motor driver ======
//====================================
//--------------------------------------------
//---- struct, enum, variable declarations ---
//--------------------------------------------
//class which controls a motor using a 'single100a' h-bridge module
enum class motorstate_t {IDLE, FWD, REV, BRAKE};
//definition of string array to be able to convert state enum to readable string (defined in motordrivers.cpp)
extern const char* motorstateStr[4];
//struct with all config parameters for single100a motor driver
typedef struct single100a_config_t {
gpio_num_t gpio_pwm;
gpio_num_t gpio_a;
gpio_num_t gpio_b;
ledc_timer_t ledc_timer;
ledc_channel_t ledc_channel;
bool aEnabledPinState;
bool bEnabledPinState;
ledc_timer_bit_t resolution;
int pwmFreq;
} single100a_config_t;
//--------------------------------
//------- single100a class -------
//--------------------------------
class single100a {
public:
//--- constructor ---
single100a(single100a_config_t config_f); //provide config struct (see above)
//--- functions ---
void set(motorstate_t state, float duty_f = 0); //set mode and duty of the motor (see motorstate_t above)
//TODO: add functions to get the current state and duty
private:
//--- functions ---
void init(); //initialize pwm and gpio outputs, calculate maxDuty
//--- variables ---
single100a_config_t config;
uint32_t dutyMax;
motorstate_t state = motorstate_t::IDLE;
};

265
board_motorctl/main/wifi.c Normal file
View File

@@ -0,0 +1,265 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "wifi.h"
//--- variables used for ap and wifi ---
static const char *TAG = "wifi";
static esp_event_handler_instance_t instance_any_id;
//============================================
//============ init nvs and netif ============
//============================================
//initialize nvs-flash and netif (needed for both AP and CLIENT)
//has to be run once at startup
void wifi_initNvs_initNetif(){
//Initialize NVS (needed for wifi)
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
}
//===========================================
//============ init access point ============
//===========================================
//--------------------------------------------
//------ configuration / declarations --------
//--------------------------------------------
#define EXAMPLE_ESP_WIFI_SSID_AP "armchair"
#define EXAMPLE_ESP_WIFI_PASS_AP ""
#define EXAMPLE_ESP_WIFI_CHANNEL_AP 1
#define EXAMPLE_MAX_STA_CONN_AP 4
static esp_netif_t *ap;
static void wifi_event_handler_ap(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
MAC2STR(event->mac), event->aid);
}
}
//-----------------------
//------ init ap --------
//-----------------------
void wifi_init_ap(void)
{
ap = esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler_ap,
NULL,
&instance_any_id));
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_WIFI_SSID_AP,
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID_AP),
.channel = EXAMPLE_ESP_WIFI_CHANNEL_AP,
.password = EXAMPLE_ESP_WIFI_PASS_AP,
.max_connection = EXAMPLE_MAX_STA_CONN_AP,
.authmode = WIFI_AUTH_WPA_WPA2_PSK
},
};
if (strlen(EXAMPLE_ESP_WIFI_PASS_AP) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
EXAMPLE_ESP_WIFI_SSID_AP, EXAMPLE_ESP_WIFI_PASS_AP, EXAMPLE_ESP_WIFI_CHANNEL_AP);
}
//=============================
//========= deinit AP =========
//=============================
void wifi_deinit_ap(void)
{
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
esp_wifi_stop();
esp_wifi_deinit();
esp_netif_destroy_default_wifi(ap);
}
//===========================================
//=============== init client ===============
//===========================================
//--------------------------------------------
//------ configuration / declarations --------
//--------------------------------------------
#define EXAMPLE_ESP_WIFI_SSID_CLIENT "BKA-network"
#define EXAMPLE_ESP_WIFI_PASS_CLIENT "airwaveslogitech410"
#define EXAMPLE_ESP_MAXIMUM_RETRY_CLIENT 10
static esp_netif_t *sta;
static esp_event_handler_instance_t instance_got_ip;
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY_CLIENT) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
//---------------------------
//------ init client --------
//---------------------------
void wifi_init_client(void)
{
s_wifi_event_group = xEventGroupCreate();
sta = esp_netif_create_default_wifi_sta();
//set static ip
esp_netif_dhcpc_stop(sta);
esp_netif_ip_info_t ip_info;
IP4_ADDR(&ip_info.ip, 10, 0, 0, 66);
IP4_ADDR(&ip_info.gw, 10, 0, 0, 1);
IP4_ADDR(&ip_info.netmask, 255, 255, 0, 0);
esp_netif_set_ip_info(sta, &ip_info);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID_CLIENT,
.password = EXAMPLE_ESP_WIFI_PASS_CLIENT,
/* Setting a password implies station will connect to all security modes including WEP/WPA.
* However these modes are deprecated and not advisable to be used. Incase your Access point
* doesn't support WPA2, these mode can be enabled by commenting below line */
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID_CLIENT, EXAMPLE_ESP_WIFI_PASS_CLIENT);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID_CLIENT, EXAMPLE_ESP_WIFI_PASS_CLIENT);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
// /* The event will not be processed after unregister */
// ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
// ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
// vEventGroupDelete(s_wifi_event_group);
}
//=================================
//========= deinit client =========
//=================================
void wifi_deinit_client(void)
{
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
esp_wifi_stop();
esp_wifi_deinit();
esp_netif_destroy_default_wifi(sta);
}

View File

@@ -0,0 +1,22 @@
#pragma once
//TODO: currently wifi names and passwords are configured in wifi.c -> move this to config?
//initialize nvs-flash and netif (needed for both AP and CLIENT)
//has to be run once at startup
//Note: this cant be put in wifi_init functions because this may not be in deinit functions
void wifi_initNvs_initNetif();
//function to start an access point
void wifi_init_ap(void);
//function to disable/deinit access point
void wifi_deinit_ap(void);
//function to connect to existing wifi network
void wifi_init_client(void);
//function to disable/deinit client
void wifi_deinit_client(void);