Merge branch 'display' into dev - mode-select menu, massage-mode, scroll status-screen
This commit is contained in:
		
						commit
						0695c92418
					
				
							
								
								
									
										36
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								README.md
									
									
									
									
									
								
							@ -1,12 +1,15 @@
 | 
			
		||||
# Overview
 | 
			
		||||
Firmware for a homemade automated electric armchair.  
 | 
			
		||||
Extensive details about this project can be found here:  
 | 
			
		||||
Extensive details about this project can be found on the website:
 | 
			
		||||
- ~~V1: [Electric Armchair V1](https://pfusch.zone/electric-armchair)~~
 | 
			
		||||
- V2: [Electric Armchair V2](https://pfusch.zone/electric-armchair-v2)
 | 
			
		||||
 | 
			
		||||
In the current version V2.2, only the esp-project in the [board_single/](board_single) folder plus the custom libraries in [common/](common) are used.  
 | 
			
		||||
Note: The projects in the folders `board_control/` and `board_motorctl/` are no longer compatible and legacy from V2.1.
 | 
			
		||||
Note: In the current version V2.2, only the esp-project in the [board_single/](board_single) folder and the custom libraries in [common/](common) are used.  
 | 
			
		||||
The projects in the folders `board_control/` and `board_motorctl/` are no longer compatible and legacy from V2.1.
 | 
			
		||||
 | 
			
		||||
<img src="doc/2023.09.09_armchair-frame.jpg" alt="Photo machine" style="width:60%;">
 | 
			
		||||
 | 
			
		||||
*Photo of the built frame that carries the armchair*
 | 
			
		||||
 | 
			
		||||
## Hardware Setup / Electrical
 | 
			
		||||
### PCB
 | 
			
		||||
@ -35,9 +38,10 @@ For more details refer to the documentation on the website.
 | 
			
		||||
- Current Measurement: Monitors current of each motor
 | 
			
		||||
- Battery Capacity: Measures battery voltage and calculates percentage according to discharge curve
 | 
			
		||||
- Fan Control: Cooling fan for motor driver activated only when needed
 | 
			
		||||
- Display:
 | 
			
		||||
- Display + Rotary encoder:
 | 
			
		||||
  - Various status screens showing battery status, speed, RPM, motor current, mode, power, duty cycle, stick data
 | 
			
		||||
  - Menu for setting various options using encoder
 | 
			
		||||
  - Menu for setting various options using encoder (options are stored persistently in nvs flash)
 | 
			
		||||
  - Menu for selecting the control mode
 | 
			
		||||
- Buzzer: Provides acoustic feedback when switching modes or interacting with menu
 | 
			
		||||
 | 
			
		||||
## Planned Features
 | 
			
		||||
@ -136,25 +140,25 @@ idf.py monitor
 | 
			
		||||
 | 
			
		||||
| Count | Type          | Action               | Description                                                                                 |
 | 
			
		||||
|-------|---------------|----------------------|---------------------------------------------------------------------------------------------|
 | 
			
		||||
| 1x long | switch mode | **MENU**             | Open menu to set various options, controlled via display and rotary encoder.                |
 | 
			
		||||
| 1x      | control      | [MASSAGE] **freeze** input | When in massage mode: lock or unlock joystick input at current position.             |
 | 
			
		||||
| 1x short, 1x long | switch mode | **ADJUST-CHAIR**     | Switch to mode where the armchair leg and backrest are controlled via joystick.   |
 | 
			
		||||
| 2x      | toggle mode  | **IDLE** <=> previous | Enable/disable chair armchair (e.g., enable after startup or switch to previous mode after timeout). |
 | 
			
		||||
| 3x      | switch mode  | **JOYSTICK**         | Switch to JOYSTICK mode, to control armchair using joystick (default).                     |
 | 
			
		||||
| 4x      | switch mode  | **HTTP**             | Switch to **remote control** via web-app `http://191.168.4.1` in wifi `armchair`.                           |
 | 
			
		||||
| 5x      |              |                      |                                                                                            |
 | 
			
		||||
| 6x      | switch mode   | **MASSAGE**         | Switch to MASSAGE mode where armchair shakes differently, depending on joystick position.  |
 | 
			
		||||
| 7x      |               |                     |                                                                                            |
 | 
			
		||||
| 1x long | switch mode | **MENU_MODE_SELECT** | Open menu for selecting the current control mode                                            |
 | 
			
		||||
| 1x      | control     | [MASSAGE] **freeze** input  | When in massage mode: lock or unlock joystick input at current position.             |
 | 
			
		||||
| 1x short, 1x long | switch mode | **ADJUST-CHAIR**  | Switch to mode where the armchair leg and backrest are controlled via joystick.      |
 | 
			
		||||
| 2x      | toggle mode | **IDLE** <=> previous| Enable/disable chair armchair (e.g., enable after startup or switch to previous mode after timeout). |
 | 
			
		||||
| 3x      | switch mode | **JOYSTICK**         | Switch to JOYSTICK mode, to control armchair using joystick (default).                      |
 | 
			
		||||
| 4x      | switch mode | **HTTP**             | Switch to **remote control** via web-app `http://191.168.4.1` in wifi `armchair`.           |
 | 
			
		||||
| 5x      | switch mode | **MENU_SETTINGS**    | Open menu to set various options, controlled via display and rotary encoder.                |
 | 
			
		||||
| 6x      | switch mode | **MASSAGE**          | Switch to MASSAGE mode where armchair shakes differently, depending on joystick position.   |
 | 
			
		||||
| 7x      |             |                      |                                                                                             |
 | 
			
		||||
| 8x      | toggle option| **deceleration limit** | Disable/enable deceleration limit (default on) => more responsive.                       |
 | 
			
		||||
| 12x     | toggle option| **alt stick mapping** | Toggle between default and alternative stick mapping (reverse direction swapped).         |
 | 
			
		||||
 | 
			
		||||
**When in MENU mode** (1x long press), the encoder controls the menu:
 | 
			
		||||
**When in MENU_SETTINGS mode** (5x click), the encoder controls the settings menu: (similar in MENU_MODE_SELECT)
 | 
			
		||||
 | 
			
		||||
| Encoder Event | Current Menu | Action                                                       |
 | 
			
		||||
|---------------|--------------|--------------------------------------------------------------|
 | 
			
		||||
| long press    | main-menu    | Exit MENU mode to previous control mode (e.g., JOYSTICK).    |
 | 
			
		||||
| long press    | value-select | Exit to main-menu without changing the value.                |
 | 
			
		||||
| click         | main-menu    | Select currently highlighted menu item -> enter value-select screen.               |
 | 
			
		||||
| click         | main-menu    | Select currently highlighted menu item -> enter value-select screen. |
 | 
			
		||||
| click         | value-select | Confirm value / run action.                                  |
 | 
			
		||||
| rotate        | main-menu    | Scroll through menu items.                                   |
 | 
			
		||||
| rotate        | value-select | Change value.                                                |
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ extern "C"
 | 
			
		||||
 | 
			
		||||
#include "button.hpp"
 | 
			
		||||
#include "encoder.hpp"
 | 
			
		||||
#include "display.hpp"
 | 
			
		||||
 | 
			
		||||
// tag for logging
 | 
			
		||||
static const char *TAG = "button";
 | 
			
		||||
@ -71,16 +72,17 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case 1:
 | 
			
		||||
        // ## switch to MENU state ##
 | 
			
		||||
        // ## switch to MENU_SETTINGS state ##
 | 
			
		||||
        if (lastPressLong)
 | 
			
		||||
        {
 | 
			
		||||
            control->changeMode(controlMode_t::MENU);
 | 
			
		||||
            ESP_LOGW(TAG, "1x long press -> clear encoder queue and change to menu mode");
 | 
			
		||||
            ESP_LOGW(TAG, "1x long press -> clear encoder queue and change to mode 'menu mode select'");
 | 
			
		||||
            buzzer->beep(5, 50, 30);
 | 
			
		||||
            // clear encoder event queue (prevent menu from exiting immediately due to long press event just happend)
 | 
			
		||||
            vTaskDelay(200 / portTICK_PERIOD_MS);
 | 
			
		||||
            //TODO move encoder queue clear to changeMode() method?
 | 
			
		||||
            rotary_encoder_event_t ev;
 | 
			
		||||
            while (xQueueReceive(encoderQueue, &ev, 0) == pdPASS);
 | 
			
		||||
            buzzer->beep(20, 20, 10);
 | 
			
		||||
            vTaskDelay(500 / portTICK_PERIOD_MS);
 | 
			
		||||
            control->changeMode(controlMode_t::MENU_MODE_SELECT);
 | 
			
		||||
        }
 | 
			
		||||
        // ## toggle joystick freeze ##
 | 
			
		||||
        else if (control->getCurrentMode() == controlMode_t::MASSAGE)
 | 
			
		||||
@ -122,6 +124,17 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){
 | 
			
		||||
            control->changeMode(controlMode_t::HTTP); //switch to HTTP mode
 | 
			
		||||
            break;
 | 
			
		||||
        
 | 
			
		||||
        case 5:
 | 
			
		||||
        // ## switch to MENU_SETTINGS state ##
 | 
			
		||||
            ESP_LOGW(TAG, "5x press -> clear encoder queue and change to mode 'menu settings'");
 | 
			
		||||
            buzzer->beep(20, 20, 10);
 | 
			
		||||
            vTaskDelay(200 / portTICK_PERIOD_MS);
 | 
			
		||||
            // clear encoder event queue (prevent menu from using previous events)
 | 
			
		||||
            rotary_encoder_event_t ev;
 | 
			
		||||
            while (xQueueReceive(encoderQueue, &ev, 0) == pdPASS);
 | 
			
		||||
            control->changeMode(controlMode_t::MENU_SETTINGS);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 6:
 | 
			
		||||
        // ## switch to MASSAGE mode ##
 | 
			
		||||
            ESP_LOGW(TAG, "switch to MASSAGE");
 | 
			
		||||
@ -156,7 +169,7 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){
 | 
			
		||||
//-----------------------------
 | 
			
		||||
//------ startHandleLoop ------
 | 
			
		||||
//-----------------------------
 | 
			
		||||
// when not in MENU mode, repeatedly receives events from encoder button
 | 
			
		||||
// when not in MENU_SETTINGS mode, repeatedly receives events from encoder button
 | 
			
		||||
// and takes the corresponding action
 | 
			
		||||
// this function has to be started once in a separate task
 | 
			
		||||
#define INPUT_TIMEOUT 500 // duration of no button events, after which action is run (implicitly also is 'long-press' time)
 | 
			
		||||
@ -164,26 +177,27 @@ void buttonCommands::startHandleLoop()
 | 
			
		||||
{
 | 
			
		||||
    //-- variables --
 | 
			
		||||
    bool isPressed = false;
 | 
			
		||||
    static rotary_encoder_event_t ev; // store event data
 | 
			
		||||
    static rotary_encoder_event_t event; // store event data
 | 
			
		||||
    // int count = 0; (from class)
 | 
			
		||||
 | 
			
		||||
    while (1)
 | 
			
		||||
    {
 | 
			
		||||
        //-- disable functionality when in menu mode --
 | 
			
		||||
        //(display task uses encoder in that mode)
 | 
			
		||||
        if (control->getCurrentMode() == controlMode_t::MENU)
 | 
			
		||||
        if (control->getCurrentMode() == controlMode_t::MENU_SETTINGS 
 | 
			
		||||
        || control->getCurrentMode() == controlMode_t::MENU_MODE_SELECT)
 | 
			
		||||
        {
 | 
			
		||||
            //do nothing every loop cycle
 | 
			
		||||
            ESP_LOGD(TAG, "in MENU mode -> button commands disabled");
 | 
			
		||||
            ESP_LOGD(TAG, "in MENU_SETTINGS or MENU_MODE_SELECT mode -> button commands disabled");
 | 
			
		||||
            vTaskDelay(1000 / portTICK_PERIOD_MS);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //-- get events from encoder --
 | 
			
		||||
        if (xQueueReceive(encoderQueue, &ev, INPUT_TIMEOUT / portTICK_PERIOD_MS))
 | 
			
		||||
        if (xQueueReceive(encoderQueue, &event, INPUT_TIMEOUT / portTICK_PERIOD_MS))
 | 
			
		||||
        {
 | 
			
		||||
            control->resetTimeout();          // user input -> reset switch to IDLE timeout
 | 
			
		||||
            switch (ev.type)
 | 
			
		||||
            switch (event.type)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            case RE_ET_BTN_PRESSED:
 | 
			
		||||
@ -196,9 +210,20 @@ void buttonCommands::startHandleLoop()
 | 
			
		||||
                ESP_LOGD(TAG, "Button released");
 | 
			
		||||
                isPressed = false; // rest stored state
 | 
			
		||||
                break;
 | 
			
		||||
            case RE_ET_CHANGED: // scroll through status pages when simply rotating encoder
 | 
			
		||||
                if (event.diff > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    display_rotateStatusPage(true, true); //select NEXT status screen, stau at last element (dont rotate to first)
 | 
			
		||||
                    buzzer->beep(1, 65, 0);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    display_rotateStatusPage(false, true); //select PREVIOUS status screen, stay at first element (dont rotate to last)
 | 
			
		||||
                    buzzer->beep(1, 65, 0);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case RE_ET_BTN_LONG_PRESSED:
 | 
			
		||||
            case RE_ET_BTN_CLICKED:
 | 
			
		||||
            case RE_ET_CHANGED:
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -99,8 +99,8 @@ sabertooth2x60_config_t sabertoothConfig = {
 | 
			
		||||
motorctl_config_t configMotorControlLeft = {
 | 
			
		||||
    .name = "left",
 | 
			
		||||
    .loggingEnabled = true,
 | 
			
		||||
    .msFadeAccel = 1500, // acceleration of the motor (ms it takes from 0% to 100%)
 | 
			
		||||
    .msFadeDecel = 1000, // deceleration of the motor (ms it takes from 100% to 0%)
 | 
			
		||||
    .msFadeAccel = 1800, // acceleration of the motor (ms it takes from 0% to 100%)
 | 
			
		||||
    .msFadeDecel = 1600, // deceleration of the motor (ms it takes from 100% to 0%)
 | 
			
		||||
    .currentLimitEnabled = false,
 | 
			
		||||
    .tractionControlSystemEnabled = false,
 | 
			
		||||
    .currentSensor_adc = ADC1_CHANNEL_4, // GPIO32
 | 
			
		||||
@ -117,8 +117,8 @@ motorctl_config_t configMotorControlLeft = {
 | 
			
		||||
motorctl_config_t configMotorControlRight = {
 | 
			
		||||
    .name = "right",
 | 
			
		||||
    .loggingEnabled = false,
 | 
			
		||||
    .msFadeAccel = 1500, // acceleration of the motor (ms it takes from 0% to 100%)
 | 
			
		||||
    .msFadeDecel = 1000, // deceleration of the motor (ms it takes from 100% to 0%)
 | 
			
		||||
    .msFadeAccel = 1800, // acceleration of the motor (ms it takes from 0% to 100%)
 | 
			
		||||
    .msFadeDecel = 1600, // deceleration of the motor (ms it takes from 100% to 0%)
 | 
			
		||||
    .currentLimitEnabled = false,
 | 
			
		||||
    .tractionControlSystemEnabled = false,
 | 
			
		||||
    .currentSensor_adc = ADC1_CHANNEL_5, // GPIO33
 | 
			
		||||
@ -179,10 +179,10 @@ joystick_config_t configJoystick = {
 | 
			
		||||
//----------------------------
 | 
			
		||||
fan_config_t configFans = {
 | 
			
		||||
    .gpio_fan = GPIO_NUM_13,
 | 
			
		||||
    .dutyThreshold = 40,
 | 
			
		||||
    .minOnMs = 1500,
 | 
			
		||||
    .minOffMs = 3000,
 | 
			
		||||
    .turnOffDelayMs = 5000,
 | 
			
		||||
    .dutyThreshold = 50,
 | 
			
		||||
    .minOnMs = 3500, // time motor duty has to be above the threshold for fans to turn on
 | 
			
		||||
    .minOffMs = 5000, // min time fans have to be off to be able to turn on again
 | 
			
		||||
    .turnOffDelayMs = 3000, // time fans continue to be on after duty is below threshold 
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -258,7 +258,7 @@ joystickGenerateCommands_config_t joystickGenerateCommands_config{
 | 
			
		||||
    //-- maxDuty --
 | 
			
		||||
    // max duty when both motors are at equal ratio e.g. driving straight forward
 | 
			
		||||
    // better to be set less than 100% to have some reserve for boosting the outer tire when turning
 | 
			
		||||
    .maxDutyStraight = 75,
 | 
			
		||||
    .maxDutyStraight = 65,
 | 
			
		||||
    //-- maxBoost --
 | 
			
		||||
    // boost is amount of duty added to maxDutyStraight to outer tire while turning
 | 
			
		||||
    // => turning: inner tire gets slower, outer tire gets faster
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,29 @@ extern "C"
 | 
			
		||||
 | 
			
		||||
//tag for logging
 | 
			
		||||
static const char * TAG = "control";
 | 
			
		||||
const char* controlModeStr[9] = {"IDLE", "JOYSTICK", "MASSAGE", "HTTP", "MQTT", "BLUETOOTH", "AUTO", "ADJUST_CHAIR", "MENU"};
 | 
			
		||||
static const char * ERROR_STR = "ERR";
 | 
			
		||||
 | 
			
		||||
const char* controlModeStr[10] = {"IDLE", "JOYSTICK", "MASSAGE", "HTTP", "MQTT", "BLUETOOTH", "AUTO", "ADJUST_CHAIR", "MENU_SETTINGS", "MENU_MODE_SELECT"};
 | 
			
		||||
const uint8_t controlModeMaxCount = sizeof(controlModeStr) / sizeof(char *);
 | 
			
		||||
#define MUTEX_TIMEOUT 10000 // restart when stuck waiting for handle() mutex
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==========================
 | 
			
		||||
//==== controlModeToStr ====
 | 
			
		||||
//==========================
 | 
			
		||||
// convert controlMode enum or mode index to string for logging, returns "ERR" when index is out of range of existing modes
 | 
			
		||||
const char * controlModeToStr(int modeIndex){
 | 
			
		||||
    // return string when in allowed range
 | 
			
		||||
    if (modeIndex >= 0 && modeIndex < controlModeMaxCount)
 | 
			
		||||
        return controlModeStr[modeIndex];
 | 
			
		||||
    else
 | 
			
		||||
        // log and return error when not in range
 | 
			
		||||
        ESP_LOGE(TAG, "controlModeToStr: mode index '%d' is not in valid range - max 0-%d", modeIndex, controlModeMaxCount);
 | 
			
		||||
        return ERROR_STR;
 | 
			
		||||
}
 | 
			
		||||
const char * controlModeToStr(controlMode_t mode){
 | 
			
		||||
    return controlModeToStr((int)mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-----------------------------
 | 
			
		||||
@ -92,7 +114,7 @@ void controlledArmchair::startHandleLoop()
 | 
			
		||||
    while (1)
 | 
			
		||||
    {
 | 
			
		||||
        // mutex to prevent race condition with actions beeing run at mode change and previous mode still beeing executed
 | 
			
		||||
        if (xSemaphoreTake(handleIteration_mutex, portMAX_DELAY) == pdTRUE)
 | 
			
		||||
        if (xSemaphoreTake(handleIteration_mutex, MUTEX_TIMEOUT / portTICK_PERIOD_MS) == pdTRUE)
 | 
			
		||||
        {
 | 
			
		||||
            //--- handle current mode ---
 | 
			
		||||
            ESP_LOGV(TAG, "control loop executing... mode='%s'", controlModeStr[(int)mode]);
 | 
			
		||||
@ -100,6 +122,10 @@ void controlledArmchair::startHandleLoop()
 | 
			
		||||
 | 
			
		||||
            xSemaphoreGive(handleIteration_mutex);
 | 
			
		||||
        } // end mutex
 | 
			
		||||
        else {
 | 
			
		||||
            ESP_LOGE(TAG, "mutex timeout - stuck in changeMode? -> RESTART");
 | 
			
		||||
            esp_restart();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //--- slow loop ---
 | 
			
		||||
        // this section is run approx every 5s (+500ms)
 | 
			
		||||
@ -271,10 +297,11 @@ void controlledArmchair::handle()
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    //------- handle MENU mode -------
 | 
			
		||||
    case controlMode_t::MENU:
 | 
			
		||||
    //------- handle MENU modes -------
 | 
			
		||||
    case controlMode_t::MENU_SETTINGS:
 | 
			
		||||
    case controlMode_t::MENU_MODE_SELECT:
 | 
			
		||||
        // nothing to do here, display task handles the menu
 | 
			
		||||
        vTaskDelay(1000 / portTICK_PERIOD_MS);
 | 
			
		||||
        vTaskDelay(500 / portTICK_PERIOD_MS);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
        // TODO: add other modes here
 | 
			
		||||
@ -409,6 +436,9 @@ void controlledArmchair::handleTimeout()
 | 
			
		||||
//function to change to a specified control mode
 | 
			
		||||
void controlledArmchair::changeMode(controlMode_t modeNew)
 | 
			
		||||
{
 | 
			
		||||
    // variable to store configured accel limit before entering massage mode, to restore it later
 | 
			
		||||
    static uint32_t massagePreviousAccel = motorLeft->getFade(fadeType_t::ACCEL);
 | 
			
		||||
    static uint32_t massagePreviousDecel = motorLeft->getFade(fadeType_t::DECEL);
 | 
			
		||||
 | 
			
		||||
    // exit if target mode is already active
 | 
			
		||||
    if (mode == modeNew)
 | 
			
		||||
@ -420,7 +450,7 @@ void controlledArmchair::changeMode(controlMode_t modeNew)
 | 
			
		||||
    // mutex to wait for current handle iteration (control-task) to finish
 | 
			
		||||
    // prevents race conditions where operations when changing mode are run but old mode gets handled still
 | 
			
		||||
    ESP_LOGI(TAG, "changeMode: waiting for current handle() iteration to finish...");
 | 
			
		||||
    if (xSemaphoreTake(handleIteration_mutex, portMAX_DELAY) == pdTRUE)
 | 
			
		||||
    if (xSemaphoreTake(handleIteration_mutex, MUTEX_TIMEOUT / portTICK_PERIOD_MS) == pdTRUE)
 | 
			
		||||
    {
 | 
			
		||||
        // copy previous mode
 | 
			
		||||
        modePrevious = mode;
 | 
			
		||||
@ -454,11 +484,11 @@ void controlledArmchair::changeMode(controlMode_t modeNew)
 | 
			
		||||
            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);
 | 
			
		||||
            motorLeft->setFade(fadeType_t::DECEL, massagePreviousDecel);
 | 
			
		||||
            motorRight->setFade(fadeType_t::DECEL, massagePreviousDecel);
 | 
			
		||||
            // restore previously set acceleration limit 
 | 
			
		||||
            motorLeft->setFade(fadeType_t::ACCEL, massagePreviousAccel);
 | 
			
		||||
            motorRight->setFade(fadeType_t::ACCEL, massagePreviousAccel);
 | 
			
		||||
            // reset frozen input state
 | 
			
		||||
            freezeInput = false;
 | 
			
		||||
            break;
 | 
			
		||||
@ -507,20 +537,24 @@ void controlledArmchair::changeMode(controlMode_t modeNew)
 | 
			
		||||
            buzzer->beep(3, 100, 50);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case controlMode_t::MENU:
 | 
			
		||||
        case controlMode_t::MENU_SETTINGS:
 | 
			
		||||
            idleBothMotors();
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case controlMode_t::MASSAGE:
 | 
			
		||||
            ESP_LOGW(TAG, "switching to MASSAGE mode -> reducing fading");
 | 
			
		||||
            uint32_t shake_msFadeAccel = 500; // TODO: move this to config
 | 
			
		||||
            uint32_t shake_msFadeAccel = 350; // TODO: move this to config
 | 
			
		||||
            uint32_t shake_msFadeDecel = 0; // TODO: move this to config
 | 
			
		||||
 | 
			
		||||
            // save currently set normal acceleration config (for restore when leavinge MASSAGE again)
 | 
			
		||||
            massagePreviousAccel = motorLeft->getFade(fadeType_t::ACCEL);
 | 
			
		||||
            massagePreviousDecel = motorLeft->getFade(fadeType_t::DECEL);
 | 
			
		||||
            // 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);
 | 
			
		||||
            motorLeft->setFade(fadeType_t::DECEL, shake_msFadeDecel, false);
 | 
			
		||||
            motorRight->setFade(fadeType_t::DECEL, shake_msFadeDecel, false);
 | 
			
		||||
            // reduce upfading (increase acceleration) but do not update nvs
 | 
			
		||||
            motorLeft->setFade(fadeType_t::ACCEL, shake_msFadeAccel, false);
 | 
			
		||||
            motorRight->setFade(fadeType_t::ACCEL, shake_msFadeAccel, false);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -530,6 +564,11 @@ void controlledArmchair::changeMode(controlMode_t modeNew)
 | 
			
		||||
        // unlock mutex for control task to continue handling modes
 | 
			
		||||
        xSemaphoreGive(handleIteration_mutex);
 | 
			
		||||
    } // end mutex
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        ESP_LOGE(TAG, "mutex timeout - stuck in handle() loop? -> RESTART");
 | 
			
		||||
        esp_restart();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO simplify the following 3 functions? can be replaced by one?
 | 
			
		||||
 | 
			
		||||
@ -20,9 +20,10 @@ extern "C"
 | 
			
		||||
//---- struct, enum, variable declarations ---
 | 
			
		||||
//--------------------------------------------
 | 
			
		||||
//enum that decides how the motors get controlled
 | 
			
		||||
enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, HTTP, MQTT, BLUETOOTH, AUTO, ADJUST_CHAIR, MENU};
 | 
			
		||||
enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, HTTP, MQTT, BLUETOOTH, AUTO, ADJUST_CHAIR, MENU_SETTINGS, MENU_MODE_SELECT};
 | 
			
		||||
//string array representing the mode enum (for printing the state as string)
 | 
			
		||||
extern const char* controlModeStr[9];
 | 
			
		||||
extern const char* controlModeStr[10];
 | 
			
		||||
extern const uint8_t controlModeMaxCount;
 | 
			
		||||
 | 
			
		||||
//--- control_config_t ---
 | 
			
		||||
//struct with config parameters
 | 
			
		||||
@ -34,6 +35,14 @@ typedef struct control_config_t {
 | 
			
		||||
} control_config_t;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==========================
 | 
			
		||||
//==== controlModeToStr ====
 | 
			
		||||
//==========================
 | 
			
		||||
// convert controlMode enum or index to string for logging
 | 
			
		||||
const char * controlModeToStr(controlMode_t mode);
 | 
			
		||||
const char * controlModeToStr(int modeIndex);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//=======================================
 | 
			
		||||
//============ control task =============
 | 
			
		||||
//=======================================
 | 
			
		||||
@ -84,8 +93,9 @@ class controlledArmchair {
 | 
			
		||||
        //function that restarts timer which initiates the automatic timeout (switch to IDLE) after certain time of inactivity
 | 
			
		||||
        void resetTimeout();
 | 
			
		||||
 | 
			
		||||
        //methods to get the current control mode
 | 
			
		||||
        //methods to get the current or previous control mode
 | 
			
		||||
        controlMode_t getCurrentMode() const {return mode;};
 | 
			
		||||
        controlMode_t getPreviousMode() const {return modePrevious;};
 | 
			
		||||
        const char *getCurrentModeStr() const { return controlModeStr[(int)mode]; };
 | 
			
		||||
 | 
			
		||||
        //--- mode specific ---
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ extern "C"{
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//=== content config ===
 | 
			
		||||
#define STARTUP_MSG_TIMEOUT 2000
 | 
			
		||||
#define STARTUP_MSG_TIMEOUT 2600
 | 
			
		||||
#define ADC_BATT_VOLTAGE ADC1_CHANNEL_6
 | 
			
		||||
#define BAT_CELL_COUNT 7
 | 
			
		||||
// continously vary display contrast from 0 to 250 in OVERVIEW status screen
 | 
			
		||||
@ -49,7 +49,7 @@ int readAdc(adc1_channel_t adc, uint32_t samples){
 | 
			
		||||
SSD1306_t dev;
 | 
			
		||||
//tag for logging
 | 
			
		||||
static const char * TAG = "display";
 | 
			
		||||
//define currently shown status page (continously displayed content when not in MENU mode)
 | 
			
		||||
//define currently shown status page (continously displayed content when not in MENU_SETTINGS mode)
 | 
			
		||||
static displayStatusPage_t selectedStatusPage = STATUS_SCREEN_OVERVIEW;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -441,6 +441,13 @@ void showStartupMsg(){
 | 
			
		||||
//============================
 | 
			
		||||
void display_selectStatusPage(displayStatusPage_t newStatusPage)
 | 
			
		||||
{
 | 
			
		||||
	// get number of available screens
 | 
			
		||||
	const displayStatusPage_t max = STATUS_SCREEN_SCREENSAVER;
 | 
			
		||||
	const uint8_t maxItems = (uint8_t)max;
 | 
			
		||||
	// limit to available pages
 | 
			
		||||
	if (newStatusPage > maxItems) newStatusPage = (displayStatusPage_t)(maxItems);
 | 
			
		||||
	else if (newStatusPage < 0) newStatusPage = (displayStatusPage_t)0;
 | 
			
		||||
 | 
			
		||||
	//-- run commands when switching FROM certain mode --
 | 
			
		||||
	switch (selectedStatusPage)
 | 
			
		||||
	{
 | 
			
		||||
@ -470,13 +477,117 @@ void display_selectStatusPage(displayStatusPage_t newStatusPage)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//============================
 | 
			
		||||
//===== rotateStatusPage =====
 | 
			
		||||
//============================
 | 
			
		||||
// select next/previous status screen and rotate to start/end (uses all available in struct)
 | 
			
		||||
void display_rotateStatusPage(bool reverseDirection, bool noRotate)
 | 
			
		||||
{
 | 
			
		||||
	// get number of available screens
 | 
			
		||||
	const displayStatusPage_t max = STATUS_SCREEN_SCREENSAVER;
 | 
			
		||||
	const uint8_t maxItems = (uint8_t)max - 1; // screensaver is not relevant
 | 
			
		||||
 | 
			
		||||
	if (reverseDirection == false) // rotate next
 | 
			
		||||
	{
 | 
			
		||||
		if (selectedStatusPage >= maxItems) // already at last item
 | 
			
		||||
		{
 | 
			
		||||
			if (noRotate)
 | 
			
		||||
				return;										  // stay at last item when rotating disabled
 | 
			
		||||
			display_selectStatusPage((displayStatusPage_t)0); // rotate to first item
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
			// select next screen
 | 
			
		||||
			display_selectStatusPage((displayStatusPage_t)((int)selectedStatusPage + 1));
 | 
			
		||||
		ssd1306_clear_screen(&dev, false); // clear screen when switching
 | 
			
		||||
	}
 | 
			
		||||
	else // rotate back
 | 
			
		||||
	{
 | 
			
		||||
		if (selectedStatusPage <= 0) // already at first item
 | 
			
		||||
		{
 | 
			
		||||
			if (noRotate)
 | 
			
		||||
				return;												   // stay at first item when rotating disabled
 | 
			
		||||
			display_selectStatusPage((displayStatusPage_t)(maxItems)); // rotate to last item
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
			// select previous screen
 | 
			
		||||
			display_selectStatusPage((displayStatusPage_t)((int)selectedStatusPage - 1));
 | 
			
		||||
		ssd1306_clear_screen(&dev, false); // clear screen when switching
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==========================
 | 
			
		||||
//=== handleStatusScreen ===
 | 
			
		||||
//==========================
 | 
			
		||||
//show currently selected status screen on display
 | 
			
		||||
//function is repeatedly called by display task when not in MENU mode
 | 
			
		||||
void handleStatusScreen(display_task_parameters_t *objects)
 | 
			
		||||
{
 | 
			
		||||
	switch (selectedStatusPage)
 | 
			
		||||
	{
 | 
			
		||||
	default:
 | 
			
		||||
	case STATUS_SCREEN_OVERVIEW:
 | 
			
		||||
		showStatusScreenOverview(objects);
 | 
			
		||||
		break;
 | 
			
		||||
	case STATUS_SCREEN_SPEED:
 | 
			
		||||
		showStatusScreenSpeed(objects);
 | 
			
		||||
		break;
 | 
			
		||||
	case STATUS_SCREEN_JOYSTICK:
 | 
			
		||||
		showStatusScreenJoystick(objects);
 | 
			
		||||
		break;
 | 
			
		||||
	case STATUS_SCREEN_MOTORS:
 | 
			
		||||
		showStatusScreenMotors(objects);
 | 
			
		||||
		break;
 | 
			
		||||
	case STATUS_SCREEN_SCREENSAVER:
 | 
			
		||||
		showStatusScreenScreensaver(objects);
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//--- handle timeouts ---
 | 
			
		||||
	uint32_t inactiveMs = objects->control->getInactivityDurationMs();
 | 
			
		||||
	//-- screensaver --
 | 
			
		||||
	// handle switch to screensaver when no user input for a long time
 | 
			
		||||
	if (inactiveMs > displayConfig.timeoutSwitchToScreensaverMs) // timeout - switch to screensaver is due
 | 
			
		||||
	{
 | 
			
		||||
		if (selectedStatusPage != STATUS_SCREEN_SCREENSAVER)
 | 
			
		||||
		{ // switch/log only once at change
 | 
			
		||||
			ESP_LOGW(TAG, "no activity for more than %d min, switching to screensaver", inactiveMs / 1000 / 60);
 | 
			
		||||
			display_selectStatusPage(STATUS_SCREEN_SCREENSAVER);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else if (selectedStatusPage == STATUS_SCREEN_SCREENSAVER) // exit screensaver when there was recent activity
 | 
			
		||||
	{
 | 
			
		||||
		ESP_LOGW(TAG, "recent activity detected, disabling screensaver");
 | 
			
		||||
		display_selectStatusPage(STATUS_SCREEN_OVERVIEW);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//-- reduce brightness --
 | 
			
		||||
	// handle brightness reduction when no user input for some time
 | 
			
		||||
	static bool brightnessIsReduced = false;
 | 
			
		||||
	if (inactiveMs > displayConfig.timeoutReduceContrastMs) // threshold exceeded - reduction of brightness is due
 | 
			
		||||
	{
 | 
			
		||||
		if (!brightnessIsReduced) // change / log only once at change
 | 
			
		||||
		{
 | 
			
		||||
			// reduce display brightness (less burn in)
 | 
			
		||||
			ESP_LOGW(TAG, "no activity for more than %d min, reducing display brightness to %d/255", inactiveMs / 1000 / 60, displayConfig.contrastReduced);
 | 
			
		||||
			ssd1306_contrast(&dev, displayConfig.contrastReduced);
 | 
			
		||||
			brightnessIsReduced = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else if (brightnessIsReduced) // threshold not exceeded anymore, but still reduced
 | 
			
		||||
	{
 | 
			
		||||
		// increase display brighness again
 | 
			
		||||
		ESP_LOGW(TAG, "recent activity detected, increasing brightness again");
 | 
			
		||||
		ssd1306_contrast(&dev, displayConfig.contrastNormal);
 | 
			
		||||
		brightnessIsReduced = false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//============================
 | 
			
		||||
//======= display task =======
 | 
			
		||||
//============================
 | 
			
		||||
// TODO: separate task for each loop?
 | 
			
		||||
 | 
			
		||||
void display_task(void *pvParameters)
 | 
			
		||||
{
 | 
			
		||||
	ESP_LOGW(TAG, "Initializing display and starting handle loop");
 | 
			
		||||
@ -492,77 +603,31 @@ void display_task(void *pvParameters)
 | 
			
		||||
	vTaskDelay(STARTUP_MSG_TIMEOUT / portTICK_PERIOD_MS);
 | 
			
		||||
	ssd1306_clear_screen(&dev, false);
 | 
			
		||||
 | 
			
		||||
	// repeatedly update display with content
 | 
			
		||||
	// repeatedly update display with content depending on current mode
 | 
			
		||||
	while (1)
 | 
			
		||||
	{
 | 
			
		||||
		if (objects->control->getCurrentMode() == controlMode_t::MENU)
 | 
			
		||||
		switch (objects->control->getCurrentMode())
 | 
			
		||||
		{
 | 
			
		||||
			//uses encoder events to control menu and updates display
 | 
			
		||||
			handleMenu(objects, &dev);
 | 
			
		||||
		}
 | 
			
		||||
		else //show selected status screen in any other mode
 | 
			
		||||
		{
 | 
			
		||||
			switch (selectedStatusPage)
 | 
			
		||||
			{
 | 
			
		||||
			default:
 | 
			
		||||
			case STATUS_SCREEN_OVERVIEW:
 | 
			
		||||
				showStatusScreenOverview(objects);
 | 
			
		||||
				break;
 | 
			
		||||
			case STATUS_SCREEN_SPEED:
 | 
			
		||||
				showStatusScreenSpeed(objects);
 | 
			
		||||
				break;
 | 
			
		||||
			case STATUS_SCREEN_JOYSTICK:
 | 
			
		||||
				showStatusScreenJoystick(objects);
 | 
			
		||||
				break;
 | 
			
		||||
			case STATUS_SCREEN_MOTORS:
 | 
			
		||||
				showStatusScreenMotors(objects);
 | 
			
		||||
				break;
 | 
			
		||||
			case STATUS_SCREEN_SCREENSAVER:
 | 
			
		||||
				showStatusScreenScreensaver(objects);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		case controlMode_t::MENU_SETTINGS:
 | 
			
		||||
			// uses encoder events to control menu (settings) and updates display
 | 
			
		||||
			handleMenu_settings(objects, &dev);
 | 
			
		||||
			break;
 | 
			
		||||
		case controlMode_t::MENU_MODE_SELECT:
 | 
			
		||||
			// uses encoder events to control menu (mode select) and updates display
 | 
			
		||||
			handleMenu_modeSelect(objects, &dev);
 | 
			
		||||
			break;
 | 
			
		||||
		default: 
 | 
			
		||||
			// show selected status screen in any other mode
 | 
			
		||||
			handleStatusScreen(objects);
 | 
			
		||||
			break;
 | 
			
		||||
		} // end mode switch-case
 | 
			
		||||
		// TODO add pages and menus here
 | 
			
		||||
	} // end while(1)
 | 
			
		||||
} // end display-task
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			//--- handle timeouts ---
 | 
			
		||||
			uint32_t inactiveMs = objects->control->getInactivityDurationMs();
 | 
			
		||||
			//-- screensaver --
 | 
			
		||||
			// handle switch to screensaver when no user input for a long time
 | 
			
		||||
			if (inactiveMs > displayConfig.timeoutSwitchToScreensaverMs) // timeout - switch to screensaver is due
 | 
			
		||||
			{
 | 
			
		||||
				if (selectedStatusPage != STATUS_SCREEN_SCREENSAVER){ // switch/log only once at change
 | 
			
		||||
					ESP_LOGW(TAG, "no activity for more than %d min, switching to screensaver", inactiveMs / 1000 / 60);
 | 
			
		||||
					display_selectStatusPage(STATUS_SCREEN_SCREENSAVER);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else if (selectedStatusPage == STATUS_SCREEN_SCREENSAVER) // exit screensaver when there was recent activity
 | 
			
		||||
			{
 | 
			
		||||
				ESP_LOGW(TAG, "recent activity detected, disabling screensaver");
 | 
			
		||||
				display_selectStatusPage(STATUS_SCREEN_OVERVIEW);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//-- reduce brightness --
 | 
			
		||||
			// handle brightness reduction when no user input for some time
 | 
			
		||||
			static bool brightnessIsReduced = false;
 | 
			
		||||
			if (inactiveMs > displayConfig.timeoutReduceContrastMs) // threshold exceeded - reduction of brightness is due
 | 
			
		||||
			{
 | 
			
		||||
				if (!brightnessIsReduced) //change / log only once at change
 | 
			
		||||
				{
 | 
			
		||||
					// reduce display brightness (less burn in)
 | 
			
		||||
					ESP_LOGW(TAG, "no activity for more than %d min, reducing display brightness to %d/255", inactiveMs / 1000 / 60, displayConfig.contrastReduced);
 | 
			
		||||
					ssd1306_contrast(&dev, displayConfig.contrastReduced);
 | 
			
		||||
					brightnessIsReduced = true;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else if (brightnessIsReduced) // threshold not exceeded anymore, but still reduced
 | 
			
		||||
			{
 | 
			
		||||
				// increase display brighness again
 | 
			
		||||
				ESP_LOGW(TAG, "recent activity detected, increasing brightness again");
 | 
			
		||||
				ssd1306_contrast(&dev, displayConfig.contrastNormal);
 | 
			
		||||
				brightnessIsReduced = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// TODO add pages and menus
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	//-----------------------------------
 | 
			
		||||
	//---- text-related example code ----
 | 
			
		||||
 | 
			
		||||
@ -53,8 +53,8 @@ typedef struct display_task_parameters_t {
 | 
			
		||||
} display_task_parameters_t;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// enum for selecting the currently shown status page (display content when not in MENU mode)
 | 
			
		||||
typedef enum displayStatusPage_t {STATUS_SCREEN_OVERVIEW=0, STATUS_SCREEN_SPEED, STATUS_SCREEN_JOYSTICK, STATUS_SCREEN_MOTORS, STATUS_SCREEN_SCREENSAVER} displayStatusPage_t;
 | 
			
		||||
// enum for selecting the currently shown status page (display content when not in MENU_SETTINGS mode)
 | 
			
		||||
typedef enum displayStatusPage_t {STATUS_SCREEN_OVERVIEW=0, STATUS_SCREEN_SPEED, STATUS_SCREEN_JOYSTICK, STATUS_SCREEN_MOTORS, STATUS_SCREEN_SCREENSAVER, __NUMBER_OF_AVAILABLE_SCREENS} displayStatusPage_t; //note: SCREENSAVER has to be last one since it is ignored by rotate and used to determine count
 | 
			
		||||
 | 
			
		||||
// get precise battery voltage (using lookup table)
 | 
			
		||||
float getBatteryVoltage();
 | 
			
		||||
@ -62,8 +62,10 @@ float getBatteryVoltage();
 | 
			
		||||
// get battery charge level in percent (using lookup table as discharge curve)
 | 
			
		||||
float getBatteryPercent();
 | 
			
		||||
 | 
			
		||||
// function to select one of the defined status screens which are shown on display when not in MENU mode
 | 
			
		||||
// function to select one of the defined status screens which are shown on display when not in MENU_SETTINGS or MENU_SELECT_MODE mode
 | 
			
		||||
void display_selectStatusPage(displayStatusPage_t newStatusPage);
 | 
			
		||||
// select next/previous status screen to be shown, when noRotate is set is stays at first/last screen
 | 
			
		||||
void display_rotateStatusPage(bool reverseDirection = false, bool noRotate = false);
 | 
			
		||||
 | 
			
		||||
//task that inititialized the display, displays welcome message 
 | 
			
		||||
//and releatedly updates the display with certain content
 | 
			
		||||
 | 
			
		||||
@ -265,7 +265,7 @@ extern "C" void app_main(void) {
 | 
			
		||||
	//------------------------------
 | 
			
		||||
	//--- create task for button ---
 | 
			
		||||
	//------------------------------
 | 
			
		||||
	//task that handles button/encoder events in any mode except 'MENU' (e.g. switch modes by pressing certain count)
 | 
			
		||||
	//task that handles button/encoder events in any mode except 'MENU_SETTINGS' and 'MENU_MODE_SELECT' (e.g. switch modes by pressing certain count)
 | 
			
		||||
	task_button_parameters_t button_param = {control, joystick, encoderQueue, motorLeft, motorRight, buzzer};
 | 
			
		||||
	xTaskCreate(&task_button, "task_button", 4096, &button_param, 3, NULL);
 | 
			
		||||
 | 
			
		||||
@ -279,7 +279,7 @@ extern "C" void app_main(void) {
 | 
			
		||||
	//-----------------------------------
 | 
			
		||||
	//----- create task for display -----
 | 
			
		||||
	//-----------------------------------
 | 
			
		||||
	//task that handles the display (show stats, handle menu in 'MENU' mode)
 | 
			
		||||
	//task that handles the display (show stats, handle menu in 'MENU_SETTINGS' and 'MENU_MODE_SELECT' mode)
 | 
			
		||||
	display_task_parameters_t display_param = {display_config, control, joystick, encoderQueue, motorLeft, motorRight, speedLeft, speedRight, buzzer, &nvsHandle};
 | 
			
		||||
	xTaskCreate(&display_task, "display_task", 3*2048, &display_param, 3, NULL);
 | 
			
		||||
 | 
			
		||||
@ -303,7 +303,7 @@ extern "C" void app_main(void) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	//--- testing force specific mode after startup ---
 | 
			
		||||
	//control->changeMode(controlMode_t::MENU);
 | 
			
		||||
	//control->changeMode(controlMode_t::MENU_SETTINGS);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -79,7 +79,7 @@ void item_calibrateJoystick_action(display_task_parameters_t *objects, SSD1306_t
 | 
			
		||||
    displayTextLineCentered(display, 1, true, false, "%s", "X-min");
 | 
			
		||||
 | 
			
		||||
    //-- loop until all positions are defined --
 | 
			
		||||
    while (running && objects->control->getCurrentMode() == controlMode_t::MENU)
 | 
			
		||||
    while (running && objects->control->getCurrentMode() == controlMode_t::MENU_SETTINGS)
 | 
			
		||||
    {
 | 
			
		||||
        // repeatedly print adc value depending on currently selected axis
 | 
			
		||||
        switch (mode)
 | 
			
		||||
@ -201,7 +201,7 @@ void item_debugJoystick_action(display_task_parameters_t * objects, SSD1306_t *
 | 
			
		||||
 | 
			
		||||
    //-- show/update values --
 | 
			
		||||
    // stop when button pressed or control state changes (timeouts to IDLE)
 | 
			
		||||
    while (running && objects->control->getCurrentMode() == controlMode_t::MENU)
 | 
			
		||||
    while (running && objects->control->getCurrentMode() == controlMode_t::MENU_SETTINGS)
 | 
			
		||||
    {
 | 
			
		||||
        // repeatedly print all joystick data
 | 
			
		||||
        joystickData_t data = objects->joystick->getData();
 | 
			
		||||
@ -658,6 +658,102 @@ void showItemList(SSD1306_t *display, int selectedItem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//----------------------------------
 | 
			
		||||
//--- getNextSelectableModeIndex ---
 | 
			
		||||
//----------------------------------
 | 
			
		||||
// local function that returns index of the next (or previous) selectable control-mode index
 | 
			
		||||
// used for mode select menu. offset defines the step size (e.g. get 3rd next menu index)
 | 
			
		||||
int getNextSelectableModeIndex(int modeIndex, bool reverseDirection = false, uint8_t offset = 1)
 | 
			
		||||
{
 | 
			
		||||
    // those modes are selectable via mode-select menu - NOTE: Add other new modes here
 | 
			
		||||
    static const controlMode_t selectableModes[] = {controlMode_t::IDLE,
 | 
			
		||||
                                             controlMode_t::JOYSTICK,
 | 
			
		||||
                                             controlMode_t::MASSAGE,
 | 
			
		||||
                                             controlMode_t::HTTP,
 | 
			
		||||
                                             controlMode_t::ADJUST_CHAIR,
 | 
			
		||||
                                             controlMode_t::MENU_SETTINGS};
 | 
			
		||||
    static const int selectableModesCount = sizeof(selectableModes) / sizeof(controlMode_t);
 | 
			
		||||
 | 
			
		||||
    // when step size is greater than 1  define new modeIndex by recursively calling the function first
 | 
			
		||||
    if (offset > 1){
 | 
			
		||||
        modeIndex = getNextSelectableModeIndex(modeIndex, reverseDirection, offset - 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // search next mode that is present in selectableModes
 | 
			
		||||
    bool rotatedAlready = false;
 | 
			
		||||
    while (1)
 | 
			
		||||
    {
 | 
			
		||||
        // try next/previous item
 | 
			
		||||
        if (reverseDirection)
 | 
			
		||||
            modeIndex--;
 | 
			
		||||
        else
 | 
			
		||||
            modeIndex++;
 | 
			
		||||
 | 
			
		||||
        // go back to start/end if last/first possible mode reached
 | 
			
		||||
        if ((!reverseDirection && modeIndex >= controlModeMaxCount) || (reverseDirection && modeIndex < 0))
 | 
			
		||||
        {
 | 
			
		||||
            // prevent deadlock when no match was found for some reason
 | 
			
		||||
            if (rotatedAlready)
 | 
			
		||||
            {
 | 
			
		||||
                ESP_LOGE(TAG, "search for selectable mode failed - no matching mode found");
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
            // go to start/end
 | 
			
		||||
            if (reverseDirection)
 | 
			
		||||
                modeIndex = controlModeMaxCount - 1;
 | 
			
		||||
            else
 | 
			
		||||
                modeIndex = 0;
 | 
			
		||||
            rotatedAlready = true;
 | 
			
		||||
        }
 | 
			
		||||
        // check if current mode index is present in allowed / selectable modes
 | 
			
		||||
        for (int j = 0; j < selectableModesCount; j++)
 | 
			
		||||
        {
 | 
			
		||||
            if (modeIndex == (int)selectableModes[j])
 | 
			
		||||
                // index matches one in the selectable modes -> success
 | 
			
		||||
                return modeIndex;
 | 
			
		||||
        }
 | 
			
		||||
        ESP_LOGV(TAG, "mode index %d is no selectable mode -> trying next", modeIndex);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//--------------------------
 | 
			
		||||
//------ showModeList ------
 | 
			
		||||
//--------------------------
 | 
			
		||||
//function that renders mode-select menu (one update)
 | 
			
		||||
void showModeList(SSD1306_t *display, int selectedMode)
 | 
			
		||||
{
 | 
			
		||||
    // TODO add blinking of a line to indicate selecting
 | 
			
		||||
 | 
			
		||||
    // line 1 " - select mode -"
 | 
			
		||||
    // line 2 "  2nd prev mode "
 | 
			
		||||
    // line 3 "    prev mode   "
 | 
			
		||||
    // line 4 "SEL MODE LARGE 1/3"
 | 
			
		||||
    // line 5 "SEL MODE LARGE 2/3"
 | 
			
		||||
    // line 6 "SEL MODE LARGE 4/3"
 | 
			
		||||
    // line 7 "    next mode   "
 | 
			
		||||
    // line 8 "  2nd next mode "
 | 
			
		||||
 | 
			
		||||
    // print title (0)
 | 
			
		||||
    displayTextLine(display, 0, false, true, "- select mode -"); // inverted
 | 
			
		||||
    // print 2nd mode before (1)
 | 
			
		||||
    displayTextLineCentered(display, 1, false, false, "%s", controlModeToStr(getNextSelectableModeIndex(selectedMode, true, 2)));
 | 
			
		||||
    // print mode before (2)
 | 
			
		||||
    displayTextLineCentered(display, 2, false, false, "%s", controlModeToStr(getNextSelectableModeIndex(selectedMode, true)));
 | 
			
		||||
    // print selected mode large (3-5)
 | 
			
		||||
    displayTextLineCentered(display, 3, true, false, "%s", controlModeToStr(selectedMode));
 | 
			
		||||
    // print mode after (6)
 | 
			
		||||
    displayTextLineCentered(display, 6, false, false, "%s", controlModeToStr(getNextSelectableModeIndex(selectedMode)));
 | 
			
		||||
    // print mode after (7)
 | 
			
		||||
    displayTextLineCentered(display, 7, false, false, "%s", controlModeToStr(getNextSelectableModeIndex(selectedMode, false, 2)));
 | 
			
		||||
    // print message (6)
 | 
			
		||||
    //displayTextLineCentered(display, 7, false, true, "click to confirm");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-----------------------------
 | 
			
		||||
//--- showValueSelectStatic ---
 | 
			
		||||
//-----------------------------
 | 
			
		||||
@ -732,14 +828,14 @@ void updateValueSelect(SSD1306_t *display, int selectedItem)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//========================
 | 
			
		||||
//====== handleMenu ======
 | 
			
		||||
//========================
 | 
			
		||||
//===========================
 | 
			
		||||
//=== handleMenu_settings ===
 | 
			
		||||
//===========================
 | 
			
		||||
//controls menu with encoder input and displays the text on oled display
 | 
			
		||||
//function is repeatedly called by display task when in menu state
 | 
			
		||||
#define QUEUE_TIMEOUT 3000 //timeout no encoder event - to not block the display loop and actually handle menu-timeout
 | 
			
		||||
#define MENU_TIMEOUT 60000 //inactivity timeout (switch to IDLE mode) note: should be smaller than IDLE timeout in control task
 | 
			
		||||
void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
void handleMenu_settings(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
{
 | 
			
		||||
    static uint32_t lastActivity = 0;
 | 
			
		||||
    static int selectedItem = 0;
 | 
			
		||||
@ -749,7 +845,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
    switch (menuState)
 | 
			
		||||
    {
 | 
			
		||||
        //-------------------------
 | 
			
		||||
        //---- State MAIN MENU ----
 | 
			
		||||
        //---- State MAIN MENU_SETTINGS ----
 | 
			
		||||
        //-------------------------
 | 
			
		||||
    case MAIN_MENU:
 | 
			
		||||
        // update display
 | 
			
		||||
@ -807,7 +903,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
                //--- exit menu mode ---
 | 
			
		||||
                // change to previous mode (e.g. JOYSTICK)
 | 
			
		||||
                objects->buzzer->beep(12, 15, 8);
 | 
			
		||||
                objects->control->toggleMode(controlMode_t::MENU); //currently already in MENU -> changes to previous mode
 | 
			
		||||
                objects->control->toggleMode(controlMode_t::MENU_SETTINGS); //currently already in MENU_SETTINGS -> changes to previous mode
 | 
			
		||||
                ssd1306_clear_screen(display, false);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
@ -892,3 +988,99 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//=============================
 | 
			
		||||
//=== handleMenu_modeSelect ===
 | 
			
		||||
//=============================
 | 
			
		||||
//controls menu for selecting the control mode with encoder input and displays the text on oled display
 | 
			
		||||
//function is repeatedly called by display task when in menu state
 | 
			
		||||
#define MENU_MODE_SEL_TIMEOUT 10000 // inactivity timeout (switch to IDLE mode) note: should be smaller than IDLE timeout in control task
 | 
			
		||||
void handleMenu_modeSelect(display_task_parameters_t *objects, SSD1306_t *display)
 | 
			
		||||
{
 | 
			
		||||
    static uint32_t lastActivity = 0;
 | 
			
		||||
    static bool firstRun = true; // track if last mode was already obtained when menu got opened
 | 
			
		||||
    static int selectedMode = (int)controlMode_t::IDLE;
 | 
			
		||||
    rotary_encoder_event_t event; // store encoder event data
 | 
			
		||||
 | 
			
		||||
    // get current mode when run for first time since last select
 | 
			
		||||
    if (firstRun)
 | 
			
		||||
    {
 | 
			
		||||
        firstRun = false;
 | 
			
		||||
        ssd1306_clear_screen(display, false);                       // clear screen initially (no artefacts of previous content)
 | 
			
		||||
        selectedMode = (int)objects->control->getPreviousMode(); // store previous mode (since current mode is MENU)
 | 
			
		||||
        ESP_LOGI(TAG, "started mode-select menu, previous active is %s", controlModeStr[(int)selectedMode]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // renders list of modes with currently selected one on display
 | 
			
		||||
    showModeList(display, selectedMode);
 | 
			
		||||
    // wait for encoder event
 | 
			
		||||
    if (xQueueReceive(objects->encoderQueue, &event, QUEUE_TIMEOUT / portTICK_PERIOD_MS))
 | 
			
		||||
    {
 | 
			
		||||
        // reset menu- and control-timeout on any encoder event
 | 
			
		||||
        lastActivity = esp_log_timestamp();
 | 
			
		||||
        objects->control->resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
        switch (event.type)
 | 
			
		||||
        {
 | 
			
		||||
        case RE_ET_CHANGED:
 | 
			
		||||
            //--- scroll in list ---
 | 
			
		||||
            if (event.diff < 0)
 | 
			
		||||
            {
 | 
			
		||||
                selectedMode = getNextSelectableModeIndex(selectedMode);
 | 
			
		||||
                objects->buzzer->beep(1, 20, 0);
 | 
			
		||||
                ESP_LOGD(TAG, "showing next item: %d '%s'", selectedMode, controlModeToStr(selectedMode));
 | 
			
		||||
            }
 | 
			
		||||
            // note: display will update at start of next run
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                selectedMode = getNextSelectableModeIndex(selectedMode, true);
 | 
			
		||||
                objects->buzzer->beep(1, 20, 0);
 | 
			
		||||
                ESP_LOGD(TAG, "showing previous item: %d '%s'", selectedMode, controlModeToStr(selectedMode));
 | 
			
		||||
                // note: display will update at start of next run
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case RE_ET_BTN_CLICKED:
 | 
			
		||||
            //--- confirm mode and exit ---
 | 
			
		||||
            objects->buzzer->beep(1, 50, 10);
 | 
			
		||||
            ESP_LOGI(TAG, "Button pressed - confirming selected mode '%s'", controlModeToStr(selectedMode));
 | 
			
		||||
            objects->control->changeMode((controlMode_t)selectedMode); //note: changeMode may take some time since it waits for control-handle loop iteration to finish which has quite large delay in menu state
 | 
			
		||||
            // clear display
 | 
			
		||||
            ssd1306_clear_screen(display, false);
 | 
			
		||||
            // reset first run
 | 
			
		||||
            firstRun = true;
 | 
			
		||||
            return; // function wont be called again due to mode change
 | 
			
		||||
 | 
			
		||||
        case RE_ET_BTN_LONG_PRESSED:
 | 
			
		||||
            //--- exit to previous mode ---
 | 
			
		||||
            // change to previous mode (e.g. JOYSTICK)
 | 
			
		||||
            objects->buzzer->beep(12, 15, 8);
 | 
			
		||||
            objects->control->changeMode(objects->control->getPreviousMode());
 | 
			
		||||
            // clear display
 | 
			
		||||
            ssd1306_clear_screen(display, false);
 | 
			
		||||
            // reset first run
 | 
			
		||||
            firstRun = true;
 | 
			
		||||
            return; // function wont be called again due to mode change
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case RE_ET_BTN_RELEASED:
 | 
			
		||||
        case RE_ET_BTN_PRESSED:
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //--- menu timeout ---
 | 
			
		||||
    // close menu and switch to IDLE mode when no encoder event occured within MENU_TIMEOUT
 | 
			
		||||
    if (esp_log_timestamp() - lastActivity > MENU_MODE_SEL_TIMEOUT)
 | 
			
		||||
    {
 | 
			
		||||
        ESP_LOGW(TAG, "TIMEOUT - no activity for more than %ds -> closing menu, switching to IDLE", MENU_TIMEOUT / 1000);
 | 
			
		||||
        // clear display
 | 
			
		||||
        ssd1306_clear_screen(display, false);
 | 
			
		||||
        // change control mode
 | 
			
		||||
        objects->control->changeMode(controlMode_t::IDLE);
 | 
			
		||||
        // reset first run
 | 
			
		||||
        firstRun = true;
 | 
			
		||||
        return; // function wont be called again due to mode change
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -30,4 +30,8 @@ typedef struct
 | 
			
		||||
    const char line7[17];  // below value
 | 
			
		||||
} menuItem_t;
 | 
			
		||||
 | 
			
		||||
void handleMenu(display_task_parameters_t * objects, SSD1306_t *display);
 | 
			
		||||
//controls menu for changing settings with encoder input and displays the text on oled display (has to be repeatedly called by display task)
 | 
			
		||||
void handleMenu_settings(display_task_parameters_t * objects, SSD1306_t *display);
 | 
			
		||||
 | 
			
		||||
//controls menu for selecting the control mode with encoder input and displays the text on oled display (has to be repeatedly called by display task)
 | 
			
		||||
void handleMenu_modeSelect(display_task_parameters_t * objects, SSD1306_t *display);
 | 
			
		||||
@ -438,13 +438,21 @@ 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_msOffMax = 60;
 | 
			
		||||
uint32_t shake_msOnMax = 120;
 | 
			
		||||
float dutyShake = 60;
 | 
			
		||||
uint32_t shake_minDelay = 20; //min time in ms motor stays on/off
 | 
			
		||||
float dutyShakeMax = 30;
 | 
			
		||||
float dutyShakeMin = 5;
 | 
			
		||||
 | 
			
		||||
inline void invertMotorDirection(motorstate_t *state)
 | 
			
		||||
{
 | 
			
		||||
    if (*state == motorstate_t::FWD)
 | 
			
		||||
        *state = motorstate_t::REV;
 | 
			
		||||
    else
 | 
			
		||||
        *state = motorstate_t::FWD;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//function that generates commands for both motors from the joystick data
 | 
			
		||||
motorCommands_t joystick_generateCommandsShaking(joystickData_t data){
 | 
			
		||||
@ -452,25 +460,29 @@ 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;
 | 
			
		||||
    static motorCommands_t commands;
 | 
			
		||||
    float ratio = fabs(data.angle) / 90; //90degree = x=0 || 0degree = y=0
 | 
			
		||||
    static uint32_t cycleCount = 0;
 | 
			
		||||
 | 
			
		||||
    //calculate on/off duration
 | 
			
		||||
    uint32_t msOn = shake_msOnMax * data.radius;
 | 
			
		||||
    uint32_t msOff = shake_msOffMax * data.radius;
 | 
			
		||||
    float msOn = (shake_msOnMax - shake_minDelay) * data.radius + shake_minDelay;
 | 
			
		||||
    float msOff = (shake_msOffMax - shake_minDelay) * data.radius + shake_minDelay;
 | 
			
		||||
    float dutyShake = (dutyShakeMax - dutyShakeMin) * ratio + dutyShakeMin;
 | 
			
		||||
 | 
			
		||||
    //evaluate state (on/off)
 | 
			
		||||
    //evaluate state (motors on/off)
 | 
			
		||||
    if (data.radius > 0 ){
 | 
			
		||||
        //currently off
 | 
			
		||||
        //currently off:
 | 
			
		||||
        if (shake_state == false){
 | 
			
		||||
            //off long enough
 | 
			
		||||
            if (esp_log_timestamp() - shake_timestamp_turnedOff > msOff) {
 | 
			
		||||
                //turn on
 | 
			
		||||
                cycleCount++;
 | 
			
		||||
                shake_state = true;
 | 
			
		||||
                shake_timestamp_turnedOn = esp_log_timestamp();
 | 
			
		||||
                ESP_LOGD(TAG_CMD, "shake: cycleCount=%d, msOn=%f, msOff=%f, radius=%f, shakeDuty=%f", cycleCount, msOn, msOff, data.radius, dutyShake);
 | 
			
		||||
            }
 | 
			
		||||
        } 
 | 
			
		||||
        //currently on
 | 
			
		||||
        //currently on:
 | 
			
		||||
        else {
 | 
			
		||||
            //on long enough
 | 
			
		||||
            if (esp_log_timestamp() - shake_timestamp_turnedOn > msOn) {
 | 
			
		||||
@ -495,79 +507,49 @@ motorCommands_t joystick_generateCommandsShaking(joystickData_t data){
 | 
			
		||||
    //    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;
 | 
			
		||||
    // force off when stick pos changes - TODO: is this necessary?
 | 
			
		||||
    static joystickPos_t stickPosPrev = joystickPos_t::CENTER;
 | 
			
		||||
    if (data.position != stickPosPrev) {
 | 
			
		||||
        ESP_LOGW(TAG, "massage: stick quadrant changed, stopping for one cycle");
 | 
			
		||||
        shake_state = false;
 | 
			
		||||
        shake_timestamp_turnedOff = esp_log_timestamp();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stickPosPrev = data.position; // update last position
 | 
			
		||||
 | 
			
		||||
    //--- handle different modes (joystick in any of 4 quadrants) ---
 | 
			
		||||
    switch (stickQuadrant){
 | 
			
		||||
    switch (data.position){
 | 
			
		||||
        // idle
 | 
			
		||||
        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");
 | 
			
		||||
            ESP_LOGD(TAG_CMD, "generate shake commands: CENTER -> idle");
 | 
			
		||||
            return commands;
 | 
			
		||||
            break;
 | 
			
		||||
            //4 different modes
 | 
			
		||||
        // shake forward/reverse
 | 
			
		||||
        case joystickPos_t::X_AXIS:
 | 
			
		||||
        case joystickPos_t::Y_AXIS:
 | 
			
		||||
        case joystickPos_t::TOP_RIGHT:
 | 
			
		||||
        case joystickPos_t::TOP_LEFT:
 | 
			
		||||
            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;
 | 
			
		||||
        // shake left right
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // change direction every second on cycle in any mode
 | 
			
		||||
    //(to not start driving on average)
 | 
			
		||||
    if (cycleCount % 2 == 0)
 | 
			
		||||
    {
 | 
			
		||||
        invertMotorDirection(&commands.left.state);
 | 
			
		||||
        invertMotorDirection(&commands.right.state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //--- turn motors on/off depending on pulsing shake variable ---
 | 
			
		||||
    if (shake_state == true){
 | 
			
		||||
@ -582,11 +564,7 @@ motorCommands_t joystick_generateCommandsShaking(joystickData_t data){
 | 
			
		||||
        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);
 | 
			
		||||
    ESP_LOGD(TAG_CMD, "motor left: state=%s, duty=%.3f, cycleCount=%d, msOn=%f, msOff=%f", motorstateStr[(int)commands.left.state], commands.left.duty, cycleCount, msOn, msOff);
 | 
			
		||||
 | 
			
		||||
    return commands;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -620,17 +620,23 @@ uint32_t controlledMotor::getFadeDefault(fadeType_t fadeType){
 | 
			
		||||
//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){
 | 
			
		||||
void controlledMotor::setFade(fadeType_t fadeType, uint32_t msFadeNew, bool writeToNvs){
 | 
			
		||||
    //TODO: mutex for msFade variable also used in handle function
 | 
			
		||||
    switch(fadeType){
 | 
			
		||||
        case fadeType_t::ACCEL:
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] changed fade-up time from %d to %d", config.name, config.msFadeAccel, msFadeNew);
 | 
			
		||||
            writeAccelDuration(msFadeNew);
 | 
			
		||||
            if (writeToNvs)
 | 
			
		||||
                writeAccelDuration(msFadeNew);
 | 
			
		||||
            else
 | 
			
		||||
                config.msFadeAccel = msFadeNew;
 | 
			
		||||
            break;
 | 
			
		||||
        case fadeType_t::DECEL:
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] changed fade-down time from %d to %d",config.name, config.msFadeDecel, msFadeNew);
 | 
			
		||||
            // write new value to nvs and update the variable
 | 
			
		||||
            writeDecelDuration(msFadeNew);
 | 
			
		||||
            if (writeToNvs)
 | 
			
		||||
                writeDecelDuration(msFadeNew);
 | 
			
		||||
            else
 | 
			
		||||
                config.msFadeDecel = msFadeNew;
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ class controlledMotor {
 | 
			
		||||
        uint32_t getFade(fadeType_t fadeType); //get currently set acceleration or deceleration fading time
 | 
			
		||||
        uint32_t getFadeDefault(fadeType_t fadeType); //get acceleration or deceleration fading time from config
 | 
			
		||||
        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
 | 
			
		||||
        void setFade(fadeType_t fadeType, uint32_t msFadeNew, bool writeToNvs = true); //set acceleration or deceleration fade time and write it to nvs by default
 | 
			
		||||
        bool toggleFade(fadeType_t fadeType); //toggle acceleration or deceleration on/off
 | 
			
		||||
 | 
			
		||||
        float getCurrentA() {return cSensor.read();}; //read current-sensor of this motor (Ampere)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/2023.09.09_armchair-frame.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/2023.09.09_armchair-frame.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 354 KiB  | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user