Merge branch 'display' into dev - mode-select menu, massage-mode, scroll status-screen
This commit is contained in:
		
						commit
						0695c92418
					
				
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							| @ -1,12 +1,15 @@ | |||||||
| # Overview | # Overview | ||||||
| Firmware for a homemade automated electric armchair.   | 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)~~ | - ~~V1: [Electric Armchair V1](https://pfusch.zone/electric-armchair)~~ | ||||||
| - V2: [Electric Armchair V2](https://pfusch.zone/electric-armchair-v2) | - 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: 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.   | ||||||
| Note: The projects in the folders `board_control/` and `board_motorctl/` are no longer compatible and legacy from V2.1. | 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 | ## Hardware Setup / Electrical | ||||||
| ### PCB | ### PCB | ||||||
| @ -35,9 +38,10 @@ For more details refer to the documentation on the website. | |||||||
| - Current Measurement: Monitors current of each motor | - Current Measurement: Monitors current of each motor | ||||||
| - Battery Capacity: Measures battery voltage and calculates percentage according to discharge curve | - Battery Capacity: Measures battery voltage and calculates percentage according to discharge curve | ||||||
| - Fan Control: Cooling fan for motor driver activated only when needed | - 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 |   - 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 | - Buzzer: Provides acoustic feedback when switching modes or interacting with menu | ||||||
| 
 | 
 | ||||||
| ## Planned Features | ## Planned Features | ||||||
| @ -136,19 +140,19 @@ idf.py monitor | |||||||
| 
 | 
 | ||||||
| | Count | Type          | Action               | Description                                                                                 | | | Count | Type          | Action               | Description                                                                                 | | ||||||
| |-------|---------------|----------------------|---------------------------------------------------------------------------------------------| | |-------|---------------|----------------------|---------------------------------------------------------------------------------------------| | ||||||
| | 1x long | switch mode | **MENU**             | Open menu to set various options, controlled via display and rotary encoder.                | | | 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      | 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.      | | | 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). | | | 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).                      | | | 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`.           | | | 4x      | switch mode | **HTTP**             | Switch to **remote control** via web-app `http://191.168.4.1` in wifi `armchair`.           | | ||||||
| | 5x      |              |                      |                                                                                            | | | 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.   | | | 6x      | switch mode | **MASSAGE**          | Switch to MASSAGE mode where armchair shakes differently, depending on joystick position.   | | ||||||
| | 7x      |             |                      |                                                                                             | | | 7x      |             |                      |                                                                                             | | ||||||
| | 8x      | toggle option| **deceleration limit** | Disable/enable deceleration limit (default on) => more responsive.                       | | | 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).         | | | 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                                                       | | | Encoder Event | Current Menu | Action                                                       | | ||||||
| |---------------|--------------|--------------------------------------------------------------| | |---------------|--------------|--------------------------------------------------------------| | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ extern "C" | |||||||
| 
 | 
 | ||||||
| #include "button.hpp" | #include "button.hpp" | ||||||
| #include "encoder.hpp" | #include "encoder.hpp" | ||||||
|  | #include "display.hpp" | ||||||
| 
 | 
 | ||||||
| // tag for logging
 | // tag for logging
 | ||||||
| static const char *TAG = "button"; | static const char *TAG = "button"; | ||||||
| @ -71,16 +72,17 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){ | |||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|     case 1: |     case 1: | ||||||
|         // ## switch to MENU state ##
 |         // ## switch to MENU_SETTINGS state ##
 | ||||||
|         if (lastPressLong) |         if (lastPressLong) | ||||||
|         { |         { | ||||||
|             control->changeMode(controlMode_t::MENU); |             ESP_LOGW(TAG, "1x long press -> clear encoder queue and change to mode 'menu mode select'"); | ||||||
|             ESP_LOGW(TAG, "1x long press -> clear encoder queue and change to menu mode"); |             buzzer->beep(5, 50, 30); | ||||||
|             // clear encoder event queue (prevent menu from exiting immediately due to long press event just happend)
 |             // 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; |             rotary_encoder_event_t ev; | ||||||
|             while (xQueueReceive(encoderQueue, &ev, 0) == pdPASS); |             while (xQueueReceive(encoderQueue, &ev, 0) == pdPASS); | ||||||
|             buzzer->beep(20, 20, 10); |             control->changeMode(controlMode_t::MENU_MODE_SELECT); | ||||||
|             vTaskDelay(500 / portTICK_PERIOD_MS); |  | ||||||
|         } |         } | ||||||
|         // ## toggle joystick freeze ##
 |         // ## toggle joystick freeze ##
 | ||||||
|         else if (control->getCurrentMode() == controlMode_t::MASSAGE) |         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
 |             control->changeMode(controlMode_t::HTTP); //switch to HTTP mode
 | ||||||
|             break; |             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: |         case 6: | ||||||
|         // ## switch to MASSAGE mode ##
 |         // ## switch to MASSAGE mode ##
 | ||||||
|             ESP_LOGW(TAG, "switch to MASSAGE"); |             ESP_LOGW(TAG, "switch to MASSAGE"); | ||||||
| @ -156,7 +169,7 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){ | |||||||
| //-----------------------------
 | //-----------------------------
 | ||||||
| //------ startHandleLoop ------
 | //------ 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
 | // and takes the corresponding action
 | ||||||
| // this function has to be started once in a separate task
 | // 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)
 | #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 --
 |     //-- variables --
 | ||||||
|     bool isPressed = false; |     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)
 |     // int count = 0; (from class)
 | ||||||
| 
 | 
 | ||||||
|     while (1) |     while (1) | ||||||
|     { |     { | ||||||
|         //-- disable functionality when in menu mode --
 |         //-- disable functionality when in menu mode --
 | ||||||
|         //(display task uses encoder in that 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
 |             //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); |             vTaskDelay(1000 / portTICK_PERIOD_MS); | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         //-- get events from encoder --
 |         //-- 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
 |             control->resetTimeout();          // user input -> reset switch to IDLE timeout
 | ||||||
|             switch (ev.type) |             switch (event.type) | ||||||
|             { |             { | ||||||
|                 break; |                 break; | ||||||
|             case RE_ET_BTN_PRESSED: |             case RE_ET_BTN_PRESSED: | ||||||
| @ -196,9 +210,20 @@ void buttonCommands::startHandleLoop() | |||||||
|                 ESP_LOGD(TAG, "Button released"); |                 ESP_LOGD(TAG, "Button released"); | ||||||
|                 isPressed = false; // rest stored state
 |                 isPressed = false; // rest stored state
 | ||||||
|                 break; |                 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_LONG_PRESSED: | ||||||
|             case RE_ET_BTN_CLICKED: |             case RE_ET_BTN_CLICKED: | ||||||
|             case RE_ET_CHANGED: |  | ||||||
|             default: |             default: | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -99,8 +99,8 @@ sabertooth2x60_config_t sabertoothConfig = { | |||||||
| motorctl_config_t configMotorControlLeft = { | motorctl_config_t configMotorControlLeft = { | ||||||
|     .name = "left", |     .name = "left", | ||||||
|     .loggingEnabled = true, |     .loggingEnabled = true, | ||||||
|     .msFadeAccel = 1500, // acceleration of the motor (ms it takes from 0% to 100%)
 |     .msFadeAccel = 1800, // acceleration of the motor (ms it takes from 0% to 100%)
 | ||||||
|     .msFadeDecel = 1000, // deceleration of the motor (ms it takes from 100% to 0%)
 |     .msFadeDecel = 1600, // deceleration of the motor (ms it takes from 100% to 0%)
 | ||||||
|     .currentLimitEnabled = false, |     .currentLimitEnabled = false, | ||||||
|     .tractionControlSystemEnabled = false, |     .tractionControlSystemEnabled = false, | ||||||
|     .currentSensor_adc = ADC1_CHANNEL_4, // GPIO32
 |     .currentSensor_adc = ADC1_CHANNEL_4, // GPIO32
 | ||||||
| @ -117,8 +117,8 @@ motorctl_config_t configMotorControlLeft = { | |||||||
| motorctl_config_t configMotorControlRight = { | motorctl_config_t configMotorControlRight = { | ||||||
|     .name = "right", |     .name = "right", | ||||||
|     .loggingEnabled = false, |     .loggingEnabled = false, | ||||||
|     .msFadeAccel = 1500, // acceleration of the motor (ms it takes from 0% to 100%)
 |     .msFadeAccel = 1800, // acceleration of the motor (ms it takes from 0% to 100%)
 | ||||||
|     .msFadeDecel = 1000, // deceleration of the motor (ms it takes from 100% to 0%)
 |     .msFadeDecel = 1600, // deceleration of the motor (ms it takes from 100% to 0%)
 | ||||||
|     .currentLimitEnabled = false, |     .currentLimitEnabled = false, | ||||||
|     .tractionControlSystemEnabled = false, |     .tractionControlSystemEnabled = false, | ||||||
|     .currentSensor_adc = ADC1_CHANNEL_5, // GPIO33
 |     .currentSensor_adc = ADC1_CHANNEL_5, // GPIO33
 | ||||||
| @ -179,10 +179,10 @@ joystick_config_t configJoystick = { | |||||||
| //----------------------------
 | //----------------------------
 | ||||||
| fan_config_t configFans = { | fan_config_t configFans = { | ||||||
|     .gpio_fan = GPIO_NUM_13, |     .gpio_fan = GPIO_NUM_13, | ||||||
|     .dutyThreshold = 40, |     .dutyThreshold = 50, | ||||||
|     .minOnMs = 1500, |     .minOnMs = 3500, // time motor duty has to be above the threshold for fans to turn on
 | ||||||
|     .minOffMs = 3000, |     .minOffMs = 5000, // min time fans have to be off to be able to turn on again
 | ||||||
|     .turnOffDelayMs = 5000, |     .turnOffDelayMs = 3000, // time fans continue to be on after duty is below threshold 
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -258,7 +258,7 @@ joystickGenerateCommands_config_t joystickGenerateCommands_config{ | |||||||
|     //-- maxDuty --
 |     //-- maxDuty --
 | ||||||
|     // max duty when both motors are at equal ratio e.g. driving straight forward
 |     // 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
 |     // better to be set less than 100% to have some reserve for boosting the outer tire when turning
 | ||||||
|     .maxDutyStraight = 75, |     .maxDutyStraight = 65, | ||||||
|     //-- maxBoost --
 |     //-- maxBoost --
 | ||||||
|     // boost is amount of duty added to maxDutyStraight to outer tire while turning
 |     // boost is amount of duty added to maxDutyStraight to outer tire while turning
 | ||||||
|     // => turning: inner tire gets slower, outer tire gets faster
 |     // => turning: inner tire gets slower, outer tire gets faster
 | ||||||
|  | |||||||
| @ -21,7 +21,29 @@ extern "C" | |||||||
| 
 | 
 | ||||||
| //tag for logging
 | //tag for logging
 | ||||||
| static const char * TAG = "control"; | 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) |     while (1) | ||||||
|     { |     { | ||||||
|         // mutex to prevent race condition with actions beeing run at mode change and previous mode still beeing executed
 |         // 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 ---
 |             //--- handle current mode ---
 | ||||||
|             ESP_LOGV(TAG, "control loop executing... mode='%s'", controlModeStr[(int)mode]); |             ESP_LOGV(TAG, "control loop executing... mode='%s'", controlModeStr[(int)mode]); | ||||||
| @ -100,6 +122,10 @@ void controlledArmchair::startHandleLoop() | |||||||
| 
 | 
 | ||||||
|             xSemaphoreGive(handleIteration_mutex); |             xSemaphoreGive(handleIteration_mutex); | ||||||
|         } // end mutex
 |         } // end mutex
 | ||||||
|  |         else { | ||||||
|  |             ESP_LOGE(TAG, "mutex timeout - stuck in changeMode? -> RESTART"); | ||||||
|  |             esp_restart(); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         //--- slow loop ---
 |         //--- slow loop ---
 | ||||||
|         // this section is run approx every 5s (+500ms)
 |         // this section is run approx every 5s (+500ms)
 | ||||||
| @ -271,10 +297,11 @@ void controlledArmchair::handle() | |||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|     //------- handle MENU mode -------
 |     //------- handle MENU modes -------
 | ||||||
|     case controlMode_t::MENU: |     case controlMode_t::MENU_SETTINGS: | ||||||
|  |     case controlMode_t::MENU_MODE_SELECT: | ||||||
|         // nothing to do here, display task handles the menu
 |         // nothing to do here, display task handles the menu
 | ||||||
|         vTaskDelay(1000 / portTICK_PERIOD_MS); |         vTaskDelay(500 / portTICK_PERIOD_MS); | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|         // TODO: add other modes here
 |         // TODO: add other modes here
 | ||||||
| @ -409,6 +436,9 @@ void controlledArmchair::handleTimeout() | |||||||
| //function to change to a specified control mode
 | //function to change to a specified control mode
 | ||||||
| void controlledArmchair::changeMode(controlMode_t modeNew) | void controlledArmchair::changeMode(controlMode_t modeNew) | ||||||
| { | { | ||||||
|  |     // 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
 |     // exit if target mode is already active
 | ||||||
|     if (mode == modeNew) |     if (mode == modeNew) | ||||||
| @ -420,7 +450,7 @@ void controlledArmchair::changeMode(controlMode_t modeNew) | |||||||
|     // mutex to wait for current handle iteration (control-task) to finish
 |     // 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
 |     // 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..."); |     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
 |         // copy previous mode
 | ||||||
|         modePrevious = 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"); |             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...
 |             // TODO: fix issue when downfading was disabled before switching to massage mode - currently it gets enabled again here...
 | ||||||
|             // enable downfading (set to default value)
 |             // enable downfading (set to default value)
 | ||||||
|             motorLeft->setFade(fadeType_t::DECEL, true); |             motorLeft->setFade(fadeType_t::DECEL, massagePreviousDecel); | ||||||
|             motorRight->setFade(fadeType_t::DECEL, true); |             motorRight->setFade(fadeType_t::DECEL, massagePreviousDecel); | ||||||
|             // set upfading to default value
 |             // restore previously set acceleration limit 
 | ||||||
|             motorLeft->setFade(fadeType_t::ACCEL, true); |             motorLeft->setFade(fadeType_t::ACCEL, massagePreviousAccel); | ||||||
|             motorRight->setFade(fadeType_t::ACCEL, true); |             motorRight->setFade(fadeType_t::ACCEL, massagePreviousAccel); | ||||||
|             // reset frozen input state
 |             // reset frozen input state
 | ||||||
|             freezeInput = false; |             freezeInput = false; | ||||||
|             break; |             break; | ||||||
| @ -507,20 +537,24 @@ void controlledArmchair::changeMode(controlMode_t modeNew) | |||||||
|             buzzer->beep(3, 100, 50); |             buzzer->beep(3, 100, 50); | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case controlMode_t::MENU: |         case controlMode_t::MENU_SETTINGS: | ||||||
|             idleBothMotors(); |             idleBothMotors(); | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case controlMode_t::MASSAGE: |         case controlMode_t::MASSAGE: | ||||||
|             ESP_LOGW(TAG, "switching to MASSAGE mode -> reducing fading"); |             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)
 |             // disable downfading (max. deceleration)
 | ||||||
|             motorLeft->setFade(fadeType_t::DECEL, false); |             motorLeft->setFade(fadeType_t::DECEL, shake_msFadeDecel, false); | ||||||
|             motorRight->setFade(fadeType_t::DECEL, false); |             motorRight->setFade(fadeType_t::DECEL, shake_msFadeDecel, false); | ||||||
|             // reduce upfading (increase acceleration)
 |             // reduce upfading (increase acceleration) but do not update nvs
 | ||||||
|             motorLeft->setFade(fadeType_t::ACCEL, shake_msFadeAccel); |             motorLeft->setFade(fadeType_t::ACCEL, shake_msFadeAccel, false); | ||||||
|             motorRight->setFade(fadeType_t::ACCEL, shake_msFadeAccel); |             motorRight->setFade(fadeType_t::ACCEL, shake_msFadeAccel, false); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -530,6 +564,11 @@ void controlledArmchair::changeMode(controlMode_t modeNew) | |||||||
|         // unlock mutex for control task to continue handling modes
 |         // unlock mutex for control task to continue handling modes
 | ||||||
|         xSemaphoreGive(handleIteration_mutex); |         xSemaphoreGive(handleIteration_mutex); | ||||||
|     } // end 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?
 | //TODO simplify the following 3 functions? can be replaced by one?
 | ||||||
|  | |||||||
| @ -20,9 +20,10 @@ extern "C" | |||||||
| //---- struct, enum, variable declarations ---
 | //---- struct, enum, variable declarations ---
 | ||||||
| //--------------------------------------------
 | //--------------------------------------------
 | ||||||
| //enum that decides how the motors get controlled
 | //enum that decides how the motors get controlled
 | ||||||
| enum class controlMode_t {IDLE, JOYSTICK, MASSAGE, HTTP, MQTT, BLUETOOTH, AUTO, 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)
 | //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 ---
 | //--- control_config_t ---
 | ||||||
| //struct with config parameters
 | //struct with config parameters
 | ||||||
| @ -34,6 +35,14 @@ typedef struct control_config_t { | |||||||
| } 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 =============
 | //============ 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
 |         //function that restarts timer which initiates the automatic timeout (switch to IDLE) after certain time of inactivity
 | ||||||
|         void resetTimeout(); |         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 getCurrentMode() const {return mode;}; | ||||||
|  |         controlMode_t getPreviousMode() const {return modePrevious;}; | ||||||
|         const char *getCurrentModeStr() const { return controlModeStr[(int)mode]; }; |         const char *getCurrentModeStr() const { return controlModeStr[(int)mode]; }; | ||||||
| 
 | 
 | ||||||
|         //--- mode specific ---
 |         //--- mode specific ---
 | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ extern "C"{ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| //=== content config ===
 | //=== content config ===
 | ||||||
| #define STARTUP_MSG_TIMEOUT 2000 | #define STARTUP_MSG_TIMEOUT 2600 | ||||||
| #define ADC_BATT_VOLTAGE ADC1_CHANNEL_6 | #define ADC_BATT_VOLTAGE ADC1_CHANNEL_6 | ||||||
| #define BAT_CELL_COUNT 7 | #define BAT_CELL_COUNT 7 | ||||||
| // continously vary display contrast from 0 to 250 in OVERVIEW status screen
 | // 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; | SSD1306_t dev; | ||||||
| //tag for logging
 | //tag for logging
 | ||||||
| static const char * TAG = "display"; | 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; | static displayStatusPage_t selectedStatusPage = STATUS_SCREEN_OVERVIEW; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -441,6 +441,13 @@ void showStartupMsg(){ | |||||||
| //============================
 | //============================
 | ||||||
| void display_selectStatusPage(displayStatusPage_t newStatusPage) | 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 --
 | 	//-- run commands when switching FROM certain mode --
 | ||||||
| 	switch (selectedStatusPage) | 	switch (selectedStatusPage) | ||||||
| 	{ | 	{ | ||||||
| @ -470,37 +477,51 @@ void display_selectStatusPage(displayStatusPage_t newStatusPage) | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| //============================
 | //============================
 | ||||||
| //======= display task =======
 | //===== rotateStatusPage =====
 | ||||||
| //============================
 | //============================
 | ||||||
| // TODO: separate task for each loop?
 | // select next/previous status screen and rotate to start/end (uses all available in struct)
 | ||||||
| 
 | void display_rotateStatusPage(bool reverseDirection, bool noRotate) | ||||||
| void display_task(void *pvParameters) |  | ||||||
| { | { | ||||||
| 	ESP_LOGW(TAG, "Initializing display and starting handle loop"); | 	// get number of available screens
 | ||||||
| 	//get struct with pointers to all needed global objects from task parameter
 | 	const displayStatusPage_t max = STATUS_SCREEN_SCREENSAVER; | ||||||
| 	display_task_parameters_t *objects = (display_task_parameters_t *)pvParameters; | 	const uint8_t maxItems = (uint8_t)max - 1; // screensaver is not relevant
 | ||||||
| 
 | 
 | ||||||
| 	// initialize display
 | 	if (reverseDirection == false) // rotate next
 | ||||||
| 	display_init(objects->displayConfig); |  | ||||||
| 	// TODO check if successfully initialized
 |  | ||||||
| 
 |  | ||||||
| 	// show startup message
 |  | ||||||
| 	showStartupMsg(); |  | ||||||
| 	vTaskDelay(STARTUP_MSG_TIMEOUT / portTICK_PERIOD_MS); |  | ||||||
| 	ssd1306_clear_screen(&dev, false); |  | ||||||
| 
 |  | ||||||
| 	// repeatedly update display with content
 |  | ||||||
| 	while (1) |  | ||||||
| 	{ | 	{ | ||||||
| 		if (objects->control->getCurrentMode() == controlMode_t::MENU) | 		if (selectedStatusPage >= maxItems) // already at last item
 | ||||||
| 		{ | 		{ | ||||||
| 			//uses encoder events to control menu and updates display
 | 			if (noRotate) | ||||||
| 			handleMenu(objects, &dev); | 				return;										  // stay at last item when rotating disabled
 | ||||||
|  | 			display_selectStatusPage((displayStatusPage_t)0); // rotate to first item
 | ||||||
| 		} | 		} | ||||||
| 		else //show selected status screen in any other mode
 | 		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) | 	switch (selectedStatusPage) | ||||||
| 	{ | 	{ | ||||||
| @ -528,7 +549,8 @@ void display_task(void *pvParameters) | |||||||
| 	// handle switch to screensaver when no user input for a long time
 | 	// handle switch to screensaver when no user input for a long time
 | ||||||
| 	if (inactiveMs > displayConfig.timeoutSwitchToScreensaverMs) // timeout - switch to screensaver is due
 | 	if (inactiveMs > displayConfig.timeoutSwitchToScreensaverMs) // timeout - switch to screensaver is due
 | ||||||
| 	{ | 	{ | ||||||
| 				if (selectedStatusPage != STATUS_SCREEN_SCREENSAVER){ // switch/log only once at change
 | 		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); | 			ESP_LOGW(TAG, "no activity for more than %d min, switching to screensaver", inactiveMs / 1000 / 60); | ||||||
| 			display_selectStatusPage(STATUS_SCREEN_SCREENSAVER); | 			display_selectStatusPage(STATUS_SCREEN_SCREENSAVER); | ||||||
| 		} | 		} | ||||||
| @ -560,9 +582,52 @@ void display_task(void *pvParameters) | |||||||
| 		brightnessIsReduced = false; | 		brightnessIsReduced = false; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 		// TODO add pages and menus
 | 
 | ||||||
| 	} | 
 | ||||||
| } | //============================
 | ||||||
|  | //======= display task =======
 | ||||||
|  | //============================
 | ||||||
|  | // TODO: separate task for each loop?
 | ||||||
|  | void display_task(void *pvParameters) | ||||||
|  | { | ||||||
|  | 	ESP_LOGW(TAG, "Initializing display and starting handle loop"); | ||||||
|  | 	//get struct with pointers to all needed global objects from task parameter
 | ||||||
|  | 	display_task_parameters_t *objects = (display_task_parameters_t *)pvParameters; | ||||||
|  | 
 | ||||||
|  | 	// initialize display
 | ||||||
|  | 	display_init(objects->displayConfig); | ||||||
|  | 	// TODO check if successfully initialized
 | ||||||
|  | 
 | ||||||
|  | 	// show startup message
 | ||||||
|  | 	showStartupMsg(); | ||||||
|  | 	vTaskDelay(STARTUP_MSG_TIMEOUT / portTICK_PERIOD_MS); | ||||||
|  | 	ssd1306_clear_screen(&dev, false); | ||||||
|  | 
 | ||||||
|  | 	// repeatedly update display with content depending on current mode
 | ||||||
|  | 	while (1) | ||||||
|  | 	{ | ||||||
|  | 		switch (objects->control->getCurrentMode()) | ||||||
|  | 		{ | ||||||
|  | 		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
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| 	//-----------------------------------
 | 	//-----------------------------------
 | ||||||
| 	//---- text-related example code ----
 | 	//---- text-related example code ----
 | ||||||
|  | |||||||
| @ -53,8 +53,8 @@ typedef struct display_task_parameters_t { | |||||||
| } display_task_parameters_t; | } display_task_parameters_t; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // enum for selecting the currently shown status page (display content when not in MENU mode)
 | // 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} displayStatusPage_t; | 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)
 | // get precise battery voltage (using lookup table)
 | ||||||
| float getBatteryVoltage(); | float getBatteryVoltage(); | ||||||
| @ -62,8 +62,10 @@ float getBatteryVoltage(); | |||||||
| // get battery charge level in percent (using lookup table as discharge curve)
 | // get battery charge level in percent (using lookup table as discharge curve)
 | ||||||
| float getBatteryPercent(); | 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); | 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 
 | //task that inititialized the display, displays welcome message 
 | ||||||
| //and releatedly updates the display with certain content
 | //and releatedly updates the display with certain content
 | ||||||
|  | |||||||
| @ -265,7 +265,7 @@ extern "C" void app_main(void) { | |||||||
| 	//------------------------------
 | 	//------------------------------
 | ||||||
| 	//--- create task for button ---
 | 	//--- 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}; | 	task_button_parameters_t button_param = {control, joystick, encoderQueue, motorLeft, motorRight, buzzer}; | ||||||
| 	xTaskCreate(&task_button, "task_button", 4096, &button_param, 3, NULL); | 	xTaskCreate(&task_button, "task_button", 4096, &button_param, 3, NULL); | ||||||
| 
 | 
 | ||||||
| @ -279,7 +279,7 @@ extern "C" void app_main(void) { | |||||||
| 	//-----------------------------------
 | 	//-----------------------------------
 | ||||||
| 	//----- create task for display -----
 | 	//----- 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}; | 	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); | 	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 ---
 | 	//--- 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"); |     displayTextLineCentered(display, 1, true, false, "%s", "X-min"); | ||||||
| 
 | 
 | ||||||
|     //-- loop until all positions are defined --
 |     //-- 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
 |         // repeatedly print adc value depending on currently selected axis
 | ||||||
|         switch (mode) |         switch (mode) | ||||||
| @ -201,7 +201,7 @@ void item_debugJoystick_action(display_task_parameters_t * objects, SSD1306_t * | |||||||
| 
 | 
 | ||||||
|     //-- show/update values --
 |     //-- show/update values --
 | ||||||
|     // stop when button pressed or control state changes (timeouts to IDLE)
 |     // 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
 |         // repeatedly print all joystick data
 | ||||||
|         joystickData_t data = objects->joystick->getData(); |         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 ---
 | //--- 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
 | //controls menu with encoder input and displays the text on oled display
 | ||||||
| //function is repeatedly called by display task when in menu state
 | //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 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
 | #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 uint32_t lastActivity = 0; | ||||||
|     static int selectedItem = 0; |     static int selectedItem = 0; | ||||||
| @ -749,7 +845,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display) | |||||||
|     switch (menuState) |     switch (menuState) | ||||||
|     { |     { | ||||||
|         //-------------------------
 |         //-------------------------
 | ||||||
|         //---- State MAIN MENU ----
 |         //---- State MAIN MENU_SETTINGS ----
 | ||||||
|         //-------------------------
 |         //-------------------------
 | ||||||
|     case MAIN_MENU: |     case MAIN_MENU: | ||||||
|         // update display
 |         // update display
 | ||||||
| @ -807,7 +903,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display) | |||||||
|                 //--- exit menu mode ---
 |                 //--- exit menu mode ---
 | ||||||
|                 // change to previous mode (e.g. JOYSTICK)
 |                 // change to previous mode (e.g. JOYSTICK)
 | ||||||
|                 objects->buzzer->beep(12, 15, 8); |                 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); |                 ssd1306_clear_screen(display, false); | ||||||
|                 break; |                 break; | ||||||
| 
 | 
 | ||||||
| @ -892,3 +988,99 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display) | |||||||
|         return; |         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
 |     const char line7[17];  // below value
 | ||||||
| } menuItem_t; | } 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; | uint32_t shake_timestamp_turnedOff = 0; | ||||||
| bool shake_state = false; | bool shake_state = false; | ||||||
| joystickPos_t lastStickPos = joystickPos_t::CENTER; | 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
 | //--- configure shake mode --- TODO: move this to config
 | ||||||
| uint32_t shake_msOffMax = 80; | uint32_t shake_msOffMax = 60; | ||||||
| uint32_t shake_msOnMax = 120; | 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
 | //function that generates commands for both motors from the joystick data
 | ||||||
| motorCommands_t joystick_generateCommandsShaking(joystickData_t data){ | motorCommands_t joystick_generateCommandsShaking(joystickData_t data){ | ||||||
| @ -452,25 +460,29 @@ motorCommands_t joystick_generateCommandsShaking(joystickData_t data){ | |||||||
|     //--- handle pulsing shake variable ---
 |     //--- handle pulsing shake variable ---
 | ||||||
|     //TODO remove this, make individual per mode?
 |     //TODO remove this, make individual per mode?
 | ||||||
|     //TODO only run this when not CENTER anyways?
 |     //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
 |     float ratio = fabs(data.angle) / 90; //90degree = x=0 || 0degree = y=0
 | ||||||
|  |     static uint32_t cycleCount = 0; | ||||||
| 
 | 
 | ||||||
|     //calculate on/off duration
 |     //calculate on/off duration
 | ||||||
|     uint32_t msOn = shake_msOnMax * data.radius; |     float msOn = (shake_msOnMax - shake_minDelay) * data.radius + shake_minDelay; | ||||||
|     uint32_t msOff = shake_msOffMax * data.radius; |     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 ){ |     if (data.radius > 0 ){ | ||||||
|         //currently off
 |         //currently off:
 | ||||||
|         if (shake_state == false){ |         if (shake_state == false){ | ||||||
|             //off long enough
 |             //off long enough
 | ||||||
|             if (esp_log_timestamp() - shake_timestamp_turnedOff > msOff) { |             if (esp_log_timestamp() - shake_timestamp_turnedOff > msOff) { | ||||||
|                 //turn on
 |                 //turn on
 | ||||||
|  |                 cycleCount++; | ||||||
|                 shake_state = true; |                 shake_state = true; | ||||||
|                 shake_timestamp_turnedOn = esp_log_timestamp(); |                 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 { |         else { | ||||||
|             //on long enough
 |             //on long enough
 | ||||||
|             if (esp_log_timestamp() - shake_timestamp_turnedOn > msOn) { |             if (esp_log_timestamp() - shake_timestamp_turnedOn > msOn) { | ||||||
| @ -495,79 +507,49 @@ motorCommands_t joystick_generateCommandsShaking(joystickData_t data){ | |||||||
|     //    float angle;
 |     //    float angle;
 | ||||||
|     //} joystickData_t;
 |     //} joystickData_t;
 | ||||||
| 
 | 
 | ||||||
|     //--- evaluate stick position --- 
 |     // force off when stick pos changes - TODO: is this necessary?
 | ||||||
|     //4 quadrants and center only - with X and Y axis as hysteresis
 |     static joystickPos_t stickPosPrev = joystickPos_t::CENTER; | ||||||
|     switch (data.position){ |     if (data.position != stickPosPrev) { | ||||||
| 
 |         ESP_LOGW(TAG, "massage: stick quadrant changed, stopping for one cycle"); | ||||||
|         case joystickPos_t::CENTER: |         shake_state = false; | ||||||
|             //immediately set to center at center
 |         shake_timestamp_turnedOff = esp_log_timestamp(); | ||||||
|             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; |  | ||||||
|     } |     } | ||||||
|             } |     stickPosPrev = data.position; // update last position
 | ||||||
|             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) ---
 |     //--- handle different modes (joystick in any of 4 quadrants) ---
 | ||||||
|     switch (stickQuadrant){ |     switch (data.position){ | ||||||
|  |         // idle
 | ||||||
|         case joystickPos_t::CENTER: |         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.left.state = motorstate_t::IDLE; | ||||||
|             commands.right.state = motorstate_t::IDLE; |             commands.right.state = motorstate_t::IDLE; | ||||||
|             commands.left.duty = 0; |             commands.left.duty = 0; | ||||||
|             commands.right.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; |             return commands; | ||||||
|             break; |             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_RIGHT: | ||||||
|  |         case joystickPos_t::TOP_LEFT: | ||||||
|             commands.left.state = motorstate_t::FWD; |             commands.left.state = motorstate_t::FWD; | ||||||
|             commands.right.state = motorstate_t::FWD; |             commands.right.state = motorstate_t::FWD; | ||||||
|             break; |             break; | ||||||
|         case joystickPos_t::TOP_LEFT: |         // shake left right
 | ||||||
|             commands.left.state = motorstate_t::REV; |  | ||||||
|             commands.right.state = motorstate_t::REV; |  | ||||||
|             break; |  | ||||||
|         case joystickPos_t::BOTTOM_LEFT: |         case joystickPos_t::BOTTOM_LEFT: | ||||||
|             commands.left.state = motorstate_t::REV; |  | ||||||
|             commands.right.state = motorstate_t::FWD; |  | ||||||
|             break; |  | ||||||
|         case joystickPos_t::BOTTOM_RIGHT: |         case joystickPos_t::BOTTOM_RIGHT: | ||||||
|             commands.left.state = motorstate_t::FWD; |             commands.left.state = motorstate_t::FWD; | ||||||
|             commands.right.state = motorstate_t::REV; |             commands.right.state = motorstate_t::REV; | ||||||
|             break; |             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 ---
 |     //--- turn motors on/off depending on pulsing shake variable ---
 | ||||||
|     if (shake_state == true){ |     if (shake_state == true){ | ||||||
| @ -582,11 +564,7 @@ motorCommands_t joystick_generateCommandsShaking(joystickData_t data){ | |||||||
|         commands.right.duty = 0; |         commands.right.duty = 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     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); | ||||||
|     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; |     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
 | //function for editing or enabling the fading/ramp of the motor control
 | ||||||
| 
 | 
 | ||||||
| //set/update fading duration/amount
 | //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
 |     //TODO: mutex for msFade variable also used in handle function
 | ||||||
|     switch(fadeType){ |     switch(fadeType){ | ||||||
|         case fadeType_t::ACCEL: |         case fadeType_t::ACCEL: | ||||||
|             ESP_LOGW(TAG, "[%s] changed fade-up time from %d to %d", config.name, config.msFadeAccel, msFadeNew); |             ESP_LOGW(TAG, "[%s] changed fade-up time from %d to %d", config.name, config.msFadeAccel, msFadeNew); | ||||||
|  |             if (writeToNvs) | ||||||
|                 writeAccelDuration(msFadeNew); |                 writeAccelDuration(msFadeNew); | ||||||
|  |             else | ||||||
|  |                 config.msFadeAccel = msFadeNew; | ||||||
|             break; |             break; | ||||||
|         case fadeType_t::DECEL: |         case fadeType_t::DECEL: | ||||||
|             ESP_LOGW(TAG, "[%s] changed fade-down time from %d to %d",config.name, config.msFadeDecel, msFadeNew); |             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
 |             // write new value to nvs and update the variable
 | ||||||
|  |             if (writeToNvs) | ||||||
|                 writeDecelDuration(msFadeNew); |                 writeDecelDuration(msFadeNew); | ||||||
|  |             else | ||||||
|  |                 config.msFadeDecel = msFadeNew; | ||||||
|             break; |             break; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ class controlledMotor { | |||||||
|         uint32_t getFade(fadeType_t fadeType); //get currently set acceleration or deceleration fading time
 |         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
 |         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, 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
 |         bool toggleFade(fadeType_t fadeType); //toggle acceleration or deceleration on/off
 | ||||||
| 
 | 
 | ||||||
|         float getCurrentA() {return cSensor.read();}; //read current-sensor of this motor (Ampere)
 |         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