Merge branch 'dev' into driving-behavior
This commit is contained in:
		
						commit
						179c608638
					
				@ -1,7 +1,6 @@
 | 
			
		||||
idf_component_register(
 | 
			
		||||
    SRCS 
 | 
			
		||||
        "main.cpp"
 | 
			
		||||
        "config.cpp"
 | 
			
		||||
        "control.cpp"
 | 
			
		||||
        "button.cpp"
 | 
			
		||||
        "fan.cpp"
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
#include "auto.hpp"
 | 
			
		||||
#include "config.hpp"
 | 
			
		||||
 | 
			
		||||
//tag for logging
 | 
			
		||||
static const char * TAG = "automatedArmchair";
 | 
			
		||||
 | 
			
		||||
@ -96,11 +96,12 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){
 | 
			
		||||
        // ## switch to ADJUST_CHAIR mode ##
 | 
			
		||||
        if (lastPressLong)
 | 
			
		||||
        {
 | 
			
		||||
            ESP_LOGW(TAG, "cmd %d: toggle ADJUST_CHAIR", count);
 | 
			
		||||
            control->toggleMode(controlMode_t::ADJUST_CHAIR);
 | 
			
		||||
            ESP_LOGW(TAG, "cmd %d: switch to ADJUST_CHAIR", count);
 | 
			
		||||
            control->changeMode(controlMode_t::ADJUST_CHAIR);
 | 
			
		||||
        }
 | 
			
		||||
        // ## toggle IDLE ##
 | 
			
		||||
            else {
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            ESP_LOGW(TAG, "cmd %d: toggle IDLE", count);
 | 
			
		||||
            control->toggleIdle(); // toggle between idle and previous/default mode
 | 
			
		||||
        }
 | 
			
		||||
@ -114,14 +115,14 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){
 | 
			
		||||
 | 
			
		||||
        case 4:
 | 
			
		||||
        // ## switch to HTTP mode ##
 | 
			
		||||
            ESP_LOGW(TAG, "cmd %d: toggle between HTTP and JOYSTICK", count);
 | 
			
		||||
            control->toggleModes(controlMode_t::HTTP, controlMode_t::JOYSTICK); //toggle between HTTP and JOYSTICK mode
 | 
			
		||||
            ESP_LOGW(TAG, "cmd %d: switch to HTTP", count);
 | 
			
		||||
            control->changeMode(controlMode_t::HTTP); //switch to HTTP mode
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 6:
 | 
			
		||||
        // ## switch to MASSAGE mode ##
 | 
			
		||||
            ESP_LOGW(TAG, "cmd %d: toggle between MASSAGE and JOYSTICK", count);
 | 
			
		||||
            control->toggleModes(controlMode_t::MASSAGE, controlMode_t::JOYSTICK); //toggle between MASSAGE and JOYSTICK mode
 | 
			
		||||
            ESP_LOGW(TAG, "switch to MASSAGE");
 | 
			
		||||
            control->changeMode(controlMode_t::MASSAGE); //switch to MASSAGE mode
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 8:
 | 
			
		||||
@ -129,7 +130,7 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){
 | 
			
		||||
            //toggle deceleration fading between on and off
 | 
			
		||||
            //decelEnabled = motorLeft->toggleFade(fadeType_t::DECEL);
 | 
			
		||||
            //motorRight->toggleFade(fadeType_t::DECEL);
 | 
			
		||||
            decelEnabled = motorLeft->toggleFade(fadeType_t::ACCEL);
 | 
			
		||||
            decelEnabled = motorLeft->toggleFade(fadeType_t::ACCEL); //TODO remove/simplify this using less functions
 | 
			
		||||
            motorRight->toggleFade(fadeType_t::ACCEL);
 | 
			
		||||
            ESP_LOGW(TAG, "cmd %d: toggle deceleration fading to: %d", count, (int)decelEnabled);
 | 
			
		||||
            if (decelEnabled){
 | 
			
		||||
@ -155,7 +156,7 @@ void buttonCommands::action (uint8_t count, bool lastPressLong){
 | 
			
		||||
// when not in MENU 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 800 // duration of no button events, after which action is run (implicitly also is 'long-press' time)
 | 
			
		||||
#define INPUT_TIMEOUT 700 // duration of no button events, after which action is run (implicitly also is 'long-press' time)
 | 
			
		||||
void buttonCommands::startHandleLoop()
 | 
			
		||||
{
 | 
			
		||||
    //-- variables --
 | 
			
		||||
@ -178,7 +179,7 @@ void buttonCommands::startHandleLoop()
 | 
			
		||||
        //-- get events from encoder --
 | 
			
		||||
        if (xQueueReceive(encoderQueue, &ev, INPUT_TIMEOUT / portTICK_PERIOD_MS))
 | 
			
		||||
        {
 | 
			
		||||
            control->resetTimeout(); //reset inactivity IDLE timeout
 | 
			
		||||
            control->resetTimeout();          // user input -> reset switch to IDLE timeout
 | 
			
		||||
            switch (ev.type)
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,6 @@
 | 
			
		||||
#include "control.hpp"
 | 
			
		||||
#include "motorctl.hpp"
 | 
			
		||||
#include "auto.hpp"
 | 
			
		||||
#include "config.hpp"
 | 
			
		||||
#include "joystick.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -132,11 +132,9 @@ motorctl_config_t configMotorControlRight = {
 | 
			
		||||
//------------------------------
 | 
			
		||||
control_config_t configControl = {
 | 
			
		||||
    .defaultMode = controlMode_t::JOYSTICK, // default mode after startup and toggling IDLE
 | 
			
		||||
    //--- timeout ---
 | 
			
		||||
    .timeoutMs = 3 * 60 * 1000, // time of inactivity after which the mode gets switched to IDLE
 | 
			
		||||
    .timeoutTolerancePer = 5,   // percentage the duty can vary between timeout checks considered still inactive
 | 
			
		||||
    //--- http mode ---
 | 
			
		||||
 | 
			
		||||
    //--- timeouts ---
 | 
			
		||||
    .timeoutSwitchToIdleMs = 5 * 60 * 1000, // time of inactivity after which the mode gets switched to IDLE
 | 
			
		||||
    .timeoutNotifyPowerStillOnMs = 6 * 60 * 60 * 1000 // time in IDLE after which buzzer beeps in certain interval (notify "forgot to turn off")
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//-------------------------------
 | 
			
		||||
@ -215,6 +213,7 @@ speedSensor_config_t speedRight_config{
 | 
			
		||||
//-------- display --------
 | 
			
		||||
//-------------------------
 | 
			
		||||
display_config_t display_config{
 | 
			
		||||
    // hardware initialization
 | 
			
		||||
    .gpio_scl = GPIO_NUM_22,
 | 
			
		||||
    .gpio_sda = GPIO_NUM_23,
 | 
			
		||||
    .gpio_reset = -1, // negative number disables reset feature
 | 
			
		||||
@ -222,7 +221,11 @@ display_config_t display_config {
 | 
			
		||||
    .height = 64,
 | 
			
		||||
    .offsetX = 2,
 | 
			
		||||
    .flip = false,
 | 
			
		||||
    .contrast = 0xff, //max: 255
 | 
			
		||||
    .contrastNormal = 170, // max: 255
 | 
			
		||||
    // display task
 | 
			
		||||
    .contrastReduced = 30,                    // max: 255
 | 
			
		||||
    .timeoutReduceContrastMs = 5 * 60 * 1000, // actions at certain inactivity
 | 
			
		||||
    .timeoutSwitchToScreensaverMs = 30 * 60 * 1000
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								board_single/main/config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								board_single/main/config.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
// outsourced macros / definitions
 | 
			
		||||
 | 
			
		||||
//-- control.cpp --
 | 
			
		||||
//#define JOYSTICK_LOG_IN_IDLE
 | 
			
		||||
@ -1,57 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "motordrivers.hpp"
 | 
			
		||||
#include "motorctl.hpp"
 | 
			
		||||
#include "joystick.hpp"
 | 
			
		||||
 | 
			
		||||
#include "gpio_evaluateSwitch.hpp"
 | 
			
		||||
#include "buzzer.hpp"
 | 
			
		||||
#include "control.hpp"
 | 
			
		||||
#include "fan.hpp"
 | 
			
		||||
#include "http.hpp"
 | 
			
		||||
#include "auto.hpp"
 | 
			
		||||
#include "speedsensor.hpp"
 | 
			
		||||
#include "chairAdjust.hpp"
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
////in IDLE mode: set loglevel for evaluatedJoystick to DEBUG 
 | 
			
		||||
////and repeatedly read joystick e.g. for manually calibrating / testing joystick
 | 
			
		||||
////#define JOYSTICK_LOG_IN_IDLE
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
////TODO outsource global variables to e.g. global.cpp and only config options here?
 | 
			
		||||
//
 | 
			
		||||
////create global controlledMotor instances for both motors
 | 
			
		||||
//extern controlledMotor motorLeft;
 | 
			
		||||
//extern controlledMotor motorRight;
 | 
			
		||||
//
 | 
			
		||||
////create global joystic instance
 | 
			
		||||
//extern evaluatedJoystick joystick;
 | 
			
		||||
//
 | 
			
		||||
////create global evaluated switch instance for button next to joystick
 | 
			
		||||
//extern gpio_evaluatedSwitch buttonJoystick;
 | 
			
		||||
//
 | 
			
		||||
////create global buzzer object
 | 
			
		||||
//extern buzzer_t buzzer;
 | 
			
		||||
//
 | 
			
		||||
////create global control object
 | 
			
		||||
//extern controlledArmchair control;
 | 
			
		||||
//
 | 
			
		||||
////create global automatedArmchair object (for auto-mode)
 | 
			
		||||
//extern automatedArmchair_c armchair;
 | 
			
		||||
//
 | 
			
		||||
////create global httpJoystick object
 | 
			
		||||
////extern httpJoystick httpJoystickMain;
 | 
			
		||||
//
 | 
			
		||||
////configuration for fans / cooling
 | 
			
		||||
//extern fan_config_t configCooling;
 | 
			
		||||
//
 | 
			
		||||
////create global objects for measuring speed
 | 
			
		||||
//extern speedSensor speedLeft;
 | 
			
		||||
//extern speedSensor speedRight;
 | 
			
		||||
//
 | 
			
		||||
////create global objects for controlling the chair position
 | 
			
		||||
//extern cControlledRest legRest;
 | 
			
		||||
//extern cControlledRest backRest;
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
@ -9,13 +9,14 @@ extern "C"
 | 
			
		||||
#include "wifi.h"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#include "config.hpp"
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "control.hpp"
 | 
			
		||||
#include "chairAdjust.hpp"
 | 
			
		||||
#include "display.hpp" // needed for getBatteryPercent()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//used definitions moved from config.hpp:
 | 
			
		||||
//#define JOYSTICK_TEST
 | 
			
		||||
//used definitions moved from config.h:
 | 
			
		||||
//#define JOYSTICK_LOG_IN_IDLE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//tag for logging
 | 
			
		||||
@ -70,7 +71,6 @@ controlledArmchair::controlledArmchair(
 | 
			
		||||
void task_control( void * pvParameters ){
 | 
			
		||||
    controlledArmchair * control = (controlledArmchair *)pvParameters;
 | 
			
		||||
    ESP_LOGW(TAG, "Initializing controlledArmchair and starting handle loop");
 | 
			
		||||
    //start handle loop (control object declared in config.hpp)
 | 
			
		||||
    control->startHandleLoop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -95,13 +95,15 @@ void controlledArmchair::startHandleLoop() {
 | 
			
		||||
                //motorLeft->setTarget(commands.left.state, commands.left.duty); 
 | 
			
		||||
                vTaskDelay(500 / portTICK_PERIOD_MS);
 | 
			
		||||
#ifdef JOYSTICK_LOG_IN_IDLE
 | 
			
		||||
				//get joystick data here (without using it)
 | 
			
		||||
				//since loglevel is DEBUG, calculation details are output
 | 
			
		||||
                joystick_l->getData();
 | 
			
		||||
                // get joystick data and log it
 | 
			
		||||
                joystickData_t data joystick_l->getData();
 | 
			
		||||
                ESP_LOGI("JOYSTICK_LOG_IN_IDLE", "x=%.3f, y=%.3f, radius=%.3f, angle=%.3f, pos=%s, adcx=%d, adcy=%d",
 | 
			
		||||
                         data.x, data.y, data.radius, data.angle,
 | 
			
		||||
                         joystickPosStr[(int)data.position],
 | 
			
		||||
                         objects->joystick->getRawX(), objects->joystick->getRawY());
 | 
			
		||||
#endif
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //------- handle JOYSTICK mode -------
 | 
			
		||||
            case controlMode_t::JOYSTICK:
 | 
			
		||||
                vTaskDelay(50 / portTICK_PERIOD_MS);
 | 
			
		||||
                //get current joystick data with getData method of evaluatedJoystick
 | 
			
		||||
@ -113,6 +115,7 @@ void controlledArmchair::startHandleLoop() {
 | 
			
		||||
                // only generate when the stick data actually changed (e.g. stick stayed in center)
 | 
			
		||||
                if (stickData.x != stickDataLast.x || stickData.y != stickDataLast.y)
 | 
			
		||||
                {
 | 
			
		||||
                    resetTimeout(); //user input -> reset switch to IDLE timeout
 | 
			
		||||
                    commands = joystick_generateCommandsDriving(stickData, &joystickGenerateCommands_config);
 | 
			
		||||
                    // apply motor commands
 | 
			
		||||
                    motorRight->setTarget(commands.right);
 | 
			
		||||
@ -121,27 +124,34 @@ void controlledArmchair::startHandleLoop() {
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    vTaskDelay(20 / portTICK_PERIOD_MS);
 | 
			
		||||
                    ESP_LOGD(TAG, "analog joystick data unchanged at %s not updating commands", joystickPosStr[(int)stickData.position]);
 | 
			
		||||
                    ESP_LOGV(TAG, "analog joystick data unchanged at %s not updating commands", joystickPosStr[(int)stickData.position]);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //------- handle MASSAGE mode -------
 | 
			
		||||
            case controlMode_t::MASSAGE:
 | 
			
		||||
                vTaskDelay(10 / portTICK_PERIOD_MS);
 | 
			
		||||
                //--- read joystick ---
 | 
			
		||||
                // only update joystick data when input not frozen
 | 
			
		||||
                if (!freezeInput){
 | 
			
		||||
                stickDataLast = stickData;
 | 
			
		||||
                if (!freezeInput)
 | 
			
		||||
                    stickData = joystick_l->getData();
 | 
			
		||||
                }
 | 
			
		||||
                //--- generate motor commands ---
 | 
			
		||||
                // only generate when the stick data actually changed (e.g. stick stayed in center)
 | 
			
		||||
                if (stickData.x != stickDataLast.x || stickData.y != stickDataLast.y)
 | 
			
		||||
                {
 | 
			
		||||
                    resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
                    // pass joystick data from getData method of evaluatedJoystick to generateCommandsShaking function
 | 
			
		||||
                    commands = joystick_generateCommandsShaking(stickData);
 | 
			
		||||
                    // apply motor commands
 | 
			
		||||
                    motorRight->setTarget(commands.right);
 | 
			
		||||
                    motorLeft->setTarget(commands.left);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //------- handle HTTP mode -------
 | 
			
		||||
            case controlMode_t::HTTP:
 | 
			
		||||
                //--- get joystick data from queue ---
 | 
			
		||||
                stickDataLast = stickData;
 | 
			
		||||
@ -152,6 +162,7 @@ void controlledArmchair::startHandleLoop() {
 | 
			
		||||
                //--- generate motor commands ---
 | 
			
		||||
                //only generate when the stick data actually changed (e.g. no new data recevied via http)
 | 
			
		||||
                if (stickData.x != stickDataLast.x || stickData.y != stickDataLast.y ){
 | 
			
		||||
                    resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
                    // Note: timeout (no data received) is handled in getData method
 | 
			
		||||
                    commands = joystick_generateCommandsDriving(stickData, &joystickGenerateCommands_config);
 | 
			
		||||
 | 
			
		||||
@ -166,6 +177,7 @@ void controlledArmchair::startHandleLoop() {
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //------- handle AUTO mode -------
 | 
			
		||||
            case controlMode_t::AUTO:
 | 
			
		||||
                vTaskDelay(20 / portTICK_PERIOD_MS);
 | 
			
		||||
               //generate commands
 | 
			
		||||
@ -206,26 +218,26 @@ void controlledArmchair::startHandleLoop() {
 | 
			
		||||
               break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //------- handle ADJUST_CHAIR mode -------
 | 
			
		||||
            case controlMode_t::ADJUST_CHAIR:
 | 
			
		||||
                vTaskDelay(100 / portTICK_PERIOD_MS);
 | 
			
		||||
                //--- read joystick ---
 | 
			
		||||
                stickDataLast = stickData;
 | 
			
		||||
                stickData = joystick_l->getData();
 | 
			
		||||
                //--- idle motors ---
 | 
			
		||||
                //commands = cmds_bothMotorsIdle; - now done once at mode change
 | 
			
		||||
                //motorRight->setTarget(commands.right.state, commands.right.duty); 
 | 
			
		||||
                //motorLeft->setTarget(commands.left.state, commands.left.duty); 
 | 
			
		||||
                //--- control armchair position with joystick input ---
 | 
			
		||||
                // dont update when stick data did not change
 | 
			
		||||
                if (stickData.x != stickDataLast.x || stickData.y != stickDataLast.y)
 | 
			
		||||
                {
 | 
			
		||||
                    resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
                    controlChairAdjustment(joystick_l->getData(), legRest, backRest);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //------- handle MENU mode -------
 | 
			
		||||
            case controlMode_t::MENU:
 | 
			
		||||
                vTaskDelay(1000 / portTICK_PERIOD_MS);
 | 
			
		||||
                //nothing to do here, display task handles the menu
 | 
			
		||||
                //--- idle motors ---
 | 
			
		||||
                //commands = cmds_bothMotorsIdle; - now done once at mode change
 | 
			
		||||
                //motorRight->setTarget(commands.right.state, commands.right.duty); 
 | 
			
		||||
                //motorLeft->setTarget(commands.left.state, commands.left.duty); 
 | 
			
		||||
                vTaskDelay(1000 / portTICK_PERIOD_MS);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
              //TODO: add other modes here
 | 
			
		||||
@ -238,8 +250,7 @@ void controlledArmchair::startHandleLoop() {
 | 
			
		||||
        if (esp_log_timestamp() - timestamp_SlowLoopLastRun > 5000) {
 | 
			
		||||
            ESP_LOGV(TAG, "running slow loop... time since last run: %.1fs", (float)(esp_log_timestamp() - timestamp_SlowLoopLastRun)/1000);
 | 
			
		||||
            timestamp_SlowLoopLastRun = esp_log_timestamp();
 | 
			
		||||
 | 
			
		||||
            //run function that detects timeout (switch to idle)
 | 
			
		||||
            //run function that detects timeout (switch to idle, or notify "forgot to turn off")
 | 
			
		||||
            handleTimeout();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -310,59 +321,58 @@ void controlledArmchair::idleBothMotors(){
 | 
			
		||||
    motorLeft->setTarget(cmd_motorIdle);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-----------------------------------
 | 
			
		||||
//---------- resetTimeout -----------
 | 
			
		||||
//-----------------------------------
 | 
			
		||||
void controlledArmchair::resetTimeout(){
 | 
			
		||||
    //TODO mutex
 | 
			
		||||
    timestamp_lastActivity = esp_log_timestamp();
 | 
			
		||||
    ESP_LOGV(TAG, "timeout: activity detected, resetting timeout");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//------------------------------------
 | 
			
		||||
//---------- handleTimeout -----------
 | 
			
		||||
//------------------------------------
 | 
			
		||||
//percentage the duty can vary since last timeout check and still counts as incative 
 | 
			
		||||
//TODO: add this to config
 | 
			
		||||
float inactivityTolerance = 10; 
 | 
			
		||||
// switch to IDLE when no activity (prevent accidential movement)
 | 
			
		||||
// notify "power still on" when in IDLE for a very long time (prevent battery drain when forgotten to turn off)
 | 
			
		||||
// this function has to be run repeatedly (can be slow interval)
 | 
			
		||||
#define TIMEOUT_POWER_STILL_ON_BEEP_INTERVAL_MS 5 * 60 * 1000 // beep every 5 minutes for someone to notice
 | 
			
		||||
#define TIMEOUT_POWER_STILL_ON_BATTERY_THRESHOLD_PERCENT 96 // only notify/beep when below certain percentage (prevent beeping when connected to charger)
 | 
			
		||||
// note: timeout durations are configured in config.cpp
 | 
			
		||||
void controlledArmchair::handleTimeout()
 | 
			
		||||
{
 | 
			
		||||
    uint32_t noActivityDurationMs = esp_log_timestamp() - timestamp_lastActivity;
 | 
			
		||||
    // log current inactivity and configured timeouts
 | 
			
		||||
    ESP_LOGD(TAG, "timeout check: last activity %dmin and %ds ago - timeout IDLE after %ds - notify after power on after %dh",
 | 
			
		||||
             noActivityDurationMs / 1000 / 60,
 | 
			
		||||
             noActivityDurationMs / 1000 % 60,
 | 
			
		||||
             config.timeoutSwitchToIdleMs / 1000,
 | 
			
		||||
             config.timeoutNotifyPowerStillOnMs / 1000 / 60 / 60);
 | 
			
		||||
 | 
			
		||||
//local function that checks whether two values differ more than a given tolerance
 | 
			
		||||
bool validateActivity(float dutyOld, float dutyNow, float tolerance){
 | 
			
		||||
    float dutyDelta = dutyNow - dutyOld;
 | 
			
		||||
    if (fabs(dutyDelta) < tolerance) {
 | 
			
		||||
        return false; //no significant activity detected
 | 
			
		||||
    } else {
 | 
			
		||||
        return true; //there was activity
 | 
			
		||||
    }
 | 
			
		||||
    // -- timeout switch to IDLE --
 | 
			
		||||
    // timeout to IDLE when not idling already
 | 
			
		||||
    if (mode != controlMode_t::IDLE && noActivityDurationMs > config.timeoutSwitchToIdleMs)
 | 
			
		||||
    {
 | 
			
		||||
        ESP_LOGW(TAG, "timeout check: [TIMEOUT], no activity for more than %ds  -> switch to IDLE", config.timeoutSwitchToIdleMs / 1000);
 | 
			
		||||
        changeMode(controlMode_t::IDLE);
 | 
			
		||||
        //TODO switch to previous status-screen when activity detected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
//function that evaluates whether there is no activity/change on the motor duty for a certain time. If so, a switch to IDLE is issued. - has to be run repeatedly in a slow interval
 | 
			
		||||
void controlledArmchair::handleTimeout(){
 | 
			
		||||
    //check for timeout only when not idling already
 | 
			
		||||
    if (mode != controlMode_t::IDLE) {
 | 
			
		||||
        //get current duty from controlled motor objects
 | 
			
		||||
        float dutyLeftNow = motorLeft->getStatus().duty;
 | 
			
		||||
        float dutyRightNow = motorRight->getStatus().duty;
 | 
			
		||||
 | 
			
		||||
        //activity detected on any of the two motors
 | 
			
		||||
        if (validateActivity(dutyLeft_lastActivity, dutyLeftNow, inactivityTolerance) 
 | 
			
		||||
                || validateActivity(dutyRight_lastActivity, dutyRightNow, inactivityTolerance)
 | 
			
		||||
           ){
 | 
			
		||||
            ESP_LOGD(TAG, "timeout check: [activity] detected since last check -> reset");
 | 
			
		||||
            //reset last duty and timestamp
 | 
			
		||||
            dutyLeft_lastActivity = dutyLeftNow;
 | 
			
		||||
            dutyRight_lastActivity = dutyRightNow;
 | 
			
		||||
            resetTimeout();
 | 
			
		||||
        }
 | 
			
		||||
        //no activity on any motor and msTimeout exceeded
 | 
			
		||||
        else if (esp_log_timestamp() - timestamp_lastActivity > config.timeoutMs){
 | 
			
		||||
            ESP_LOGI(TAG, "timeout check: [TIMEOUT], no activity for more than %.ds  -> switch to idle", config.timeoutMs/1000);
 | 
			
		||||
            //toggle to idle mode
 | 
			
		||||
            toggleIdle();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            ESP_LOGD(TAG, "timeout check: [inactive], last activity %.1f s ago, timeout after %d s", (float)(esp_log_timestamp() - timestamp_lastActivity)/1000, config.timeoutMs/1000);
 | 
			
		||||
    // -- timeout notify "forgot to turn off" --
 | 
			
		||||
    // repeatedly notify via buzzer when in IDLE for a very long time to prevent battery drain ("forgot to turn off")
 | 
			
		||||
    // also battery charge-level has to be below certain threshold to prevent beeping in case connected to charger
 | 
			
		||||
    // note: ignores user input while in IDLE (e.g. encoder rotation)
 | 
			
		||||
    else if ((esp_log_timestamp() - timestamp_lastModeChange) > config.timeoutNotifyPowerStillOnMs && getBatteryPercent() < TIMEOUT_POWER_STILL_ON_BATTERY_THRESHOLD_PERCENT)
 | 
			
		||||
    {
 | 
			
		||||
        // beep in certain intervals
 | 
			
		||||
        if ((esp_log_timestamp() - timestamp_lastTimeoutBeep) > TIMEOUT_POWER_STILL_ON_BEEP_INTERVAL_MS)
 | 
			
		||||
        {
 | 
			
		||||
            ESP_LOGW(TAG, "timeout: [TIMEOUT] in IDLE since %.3f hours -> beeping", (float)(esp_log_timestamp() - timestamp_lastModeChange) / 1000 / 60 / 60);
 | 
			
		||||
            // TODO dont beep at certain time ranges (e.g. at night)
 | 
			
		||||
            timestamp_lastTimeoutBeep = esp_log_timestamp();
 | 
			
		||||
            buzzer->beep(6, 100, 50);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -374,8 +384,6 @@ void controlledArmchair::handleTimeout(){
 | 
			
		||||
//-----------------------------------
 | 
			
		||||
//function to change to a specified control mode
 | 
			
		||||
void controlledArmchair::changeMode(controlMode_t modeNew) {
 | 
			
		||||
    //reset timeout timer
 | 
			
		||||
    resetTimeout();
 | 
			
		||||
 | 
			
		||||
    //exit if target mode is already active
 | 
			
		||||
    if (mode == modeNew) {
 | 
			
		||||
@ -385,6 +393,8 @@ void controlledArmchair::changeMode(controlMode_t modeNew) {
 | 
			
		||||
 | 
			
		||||
    //copy previous mode
 | 
			
		||||
    modePrevious = mode;
 | 
			
		||||
    //store time changed (needed for timeout)
 | 
			
		||||
    timestamp_lastModeChange = esp_log_timestamp();
 | 
			
		||||
 | 
			
		||||
	ESP_LOGW(TAG, "=== changing mode from %s to %s ===", controlModeStr[(int)mode], controlModeStr[(int)modeNew]);
 | 
			
		||||
 | 
			
		||||
@ -403,6 +413,11 @@ void controlledArmchair::changeMode(controlMode_t modeNew) {
 | 
			
		||||
            buzzer->beep(1,200,100);
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
        case controlMode_t::HTTP:
 | 
			
		||||
            ESP_LOGW(TAG, "switching from HTTP mode -> stopping wifi-ap");
 | 
			
		||||
            wifi_stop_ap();
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case controlMode_t::MASSAGE:
 | 
			
		||||
            ESP_LOGW(TAG, "switching from MASSAGE mode -> restoring fading, reset frozen input");
 | 
			
		||||
            //TODO: fix issue when downfading was disabled before switching to massage mode - currently it gets enabled again here...
 | 
			
		||||
@ -448,9 +463,11 @@ void controlledArmchair::changeMode(controlMode_t modeNew) {
 | 
			
		||||
            ESP_LOGW(TAG, "switching to IDLE mode: turning both motors off, beep");
 | 
			
		||||
            idleBothMotors();
 | 
			
		||||
            buzzer->beep(1, 900, 0);
 | 
			
		||||
#ifdef JOYSTICK_LOG_IN_IDLE
 | 
			
		||||
            esp_log_level_set("evaluatedJoystick", ESP_LOG_DEBUG);
 | 
			
		||||
#endif
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case controlMode_t::HTTP:
 | 
			
		||||
            ESP_LOGW(TAG, "switching to HTTP mode -> starting wifi-ap");
 | 
			
		||||
            wifi_start_ap();
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case controlMode_t::ADJUST_CHAIR:
 | 
			
		||||
@ -540,9 +557,9 @@ void controlledArmchair::toggleMode(controlMode_t modePrimary){
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//-------------------------------
 | 
			
		||||
//------ loadDecelDuration ------
 | 
			
		||||
//-------------------------------
 | 
			
		||||
//-----------------------------
 | 
			
		||||
//-------- loadMaxDuty --------
 | 
			
		||||
//-----------------------------
 | 
			
		||||
// update local config value when maxDuty is stored in nvs
 | 
			
		||||
void controlledArmchair::loadMaxDuty(void)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,8 @@ extern const char* controlModeStr[9];
 | 
			
		||||
typedef struct control_config_t {
 | 
			
		||||
    controlMode_t defaultMode;  //default mode after startup and toggling IDLE
 | 
			
		||||
    //timeout options
 | 
			
		||||
    uint32_t timeoutMs;         //time of inactivity after which the mode gets switched to IDLE
 | 
			
		||||
    float timeoutTolerancePer;  //percentage the duty can vary between timeout checks considered still inactive
 | 
			
		||||
    uint32_t timeoutSwitchToIdleMs;         //time of inactivity after which the mode gets switched to IDLE
 | 
			
		||||
    uint32_t timeoutNotifyPowerStillOnMs;
 | 
			
		||||
} control_config_t;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -96,6 +96,8 @@ class controlledArmchair {
 | 
			
		||||
        void setMaxDuty(float maxDutyNew) { writeMaxDuty(maxDutyNew); };
 | 
			
		||||
        float getMaxDuty() const {return joystickGenerateCommands_config.maxDuty; };
 | 
			
		||||
 | 
			
		||||
        uint32_t getInactivityDurationMs() {return esp_log_timestamp() - timestamp_lastActivity;};
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
        //--- functions ---
 | 
			
		||||
@ -169,10 +171,10 @@ class controlledArmchair {
 | 
			
		||||
        //variable for slow loop
 | 
			
		||||
        uint32_t timestamp_SlowLoopLastRun = 0;
 | 
			
		||||
 | 
			
		||||
        //variables for detecting timeout (switch to idle, after inactivity)
 | 
			
		||||
        float dutyLeft_lastActivity = 0;
 | 
			
		||||
        float dutyRight_lastActivity = 0;
 | 
			
		||||
        //variables for detecting timeout (switch to idle, or notify "forgot to turn off" after inactivity
 | 
			
		||||
        uint32_t timestamp_lastModeChange = 0;
 | 
			
		||||
        uint32_t timestamp_lastActivity = 0;
 | 
			
		||||
        uint32_t timestamp_lastTimeoutBeep = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,16 @@ extern "C"{
 | 
			
		||||
#define STARTUP_MSG_TIMEOUT 2000
 | 
			
		||||
#define ADC_BATT_VOLTAGE ADC1_CHANNEL_6
 | 
			
		||||
#define BAT_CELL_COUNT 7
 | 
			
		||||
// continously vary display contrast from 0 to 250 in OVERVIEW status screen
 | 
			
		||||
//#define BRIGHTNESS_TEST
 | 
			
		||||
 | 
			
		||||
// if display and driver support hardware scrolling the SCREENSAVER status-screen will be smoother:
 | 
			
		||||
//#define HARDWARE_SCROLL_AVAILABLE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//=== variables ===
 | 
			
		||||
// every function can access the display configuration from config.cpp
 | 
			
		||||
static display_config_t displayConfig;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//--------------------------
 | 
			
		||||
@ -61,7 +70,10 @@ void display_init(display_config_t config){
 | 
			
		||||
	ssd1306_init(&dev, config.width, config.height, config.offsetX);
 | 
			
		||||
 | 
			
		||||
	ssd1306_clear_screen(&dev, false);
 | 
			
		||||
	ssd1306_contrast(&dev, config.contrast);
 | 
			
		||||
	ssd1306_contrast(&dev, config.contrastNormal);
 | 
			
		||||
 | 
			
		||||
	//store configuration locally (e.g. for accessing timeouts)
 | 
			
		||||
	displayConfig = config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -233,7 +245,7 @@ float getBatteryPercent()
 | 
			
		||||
//#############################
 | 
			
		||||
//shows overview on entire display:
 | 
			
		||||
//Battery percentage, voltage, current, mode, rpm, speed
 | 
			
		||||
#define STATUS_SCREEN_OVERVIEW_UPDATE_INTERVAL 500
 | 
			
		||||
#define STATUS_SCREEN_OVERVIEW_UPDATE_INTERVAL 400
 | 
			
		||||
void showStatusScreenOverview(display_task_parameters_t *objects)
 | 
			
		||||
{
 | 
			
		||||
	//-- battery percentage --
 | 
			
		||||
@ -263,6 +275,17 @@ void showStatusScreenOverview(display_task_parameters_t * objects)
 | 
			
		||||
				   objects->speedLeft->getRpm(),
 | 
			
		||||
				   objects->speedRight->getRpm());
 | 
			
		||||
	vTaskDelay(STATUS_SCREEN_OVERVIEW_UPDATE_INTERVAL / portTICK_PERIOD_MS);
 | 
			
		||||
 | 
			
		||||
	//-- brightness test --
 | 
			
		||||
#ifdef BRIGHTNESS_TEST
 | 
			
		||||
	// continously vary brightness/contrast for testing
 | 
			
		||||
	displayConfig.contrastNormal += 10;
 | 
			
		||||
	if (displayConfig.contrastNormal > 255)
 | 
			
		||||
		displayConfig.contrastNormal = 0;
 | 
			
		||||
	ssd1306_contrast(&dev, displayConfig.contrastNormal);
 | 
			
		||||
	vTaskDelay(100 / portTICK_PERIOD_MS);
 | 
			
		||||
	ESP_LOGW(TAG, "TEST BRIGHTNESS, setting to %d", displayConfig.contrastNormal);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -315,8 +338,6 @@ void showStatusScreenJoystick(display_task_parameters_t * objects)
 | 
			
		||||
#define STATUS_SCREEN_MOTORS_UPDATE_INTERVAL 150
 | 
			
		||||
void showStatusScreenMotors(display_task_parameters_t *objects)
 | 
			
		||||
{
 | 
			
		||||
		// print all joystick data
 | 
			
		||||
		joystickData_t data = objects->joystick->getData();
 | 
			
		||||
		displayTextLine(&dev, 0, true, false, "%-4.0fW ", fabs(objects->motorLeft->getCurrentA()) * getBatteryVoltage());
 | 
			
		||||
		displayTextLine(&dev, 3, true, false, "%-4.0fW ", fabs(objects->motorRight->getCurrentA()) * getBatteryVoltage());
 | 
			
		||||
		//displayTextLine(&dev, 0, true, false, "L:%02.0f%%", objects->motorLeft->getStatus().duty);
 | 
			
		||||
@ -331,6 +352,71 @@ void showStatusScreenMotors(display_task_parameters_t *objects)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ################################
 | 
			
		||||
// #### showScreen Screensaver ####
 | 
			
		||||
// ################################
 | 
			
		||||
//  show inactivity duration and battery perventage scrolling across screen the entire screen to prevent burn in
 | 
			
		||||
#define STATUS_SCREEN_SCREENSAVER_DELAY_NEXT_LINE_MS 10 * 1000
 | 
			
		||||
#define STATUS_SCREEN_SCREENSAVER_UPDATE_INTERVAL 500
 | 
			
		||||
#define DISPLAY_HORIZONTAL_CHARACTER_COUNT 16
 | 
			
		||||
#define DISPLAY_VERTICAL_LINE_COUNT 8
 | 
			
		||||
void showStatusScreenScreensaver(display_task_parameters_t *objects)
 | 
			
		||||
{
 | 
			
		||||
	//-- variables for line rotation --
 | 
			
		||||
	static int msPassed = 0;
 | 
			
		||||
	static int currentLine = 0;
 | 
			
		||||
	static bool lineChanging = false;
 | 
			
		||||
	// clear display once when rotating to next line
 | 
			
		||||
	if (lineChanging)
 | 
			
		||||
	{
 | 
			
		||||
		ssd1306_clear_screen(&dev, false);
 | 
			
		||||
		lineChanging = false;
 | 
			
		||||
	}
 | 
			
		||||
	//-- print 2 lines scrolling horizontally --
 | 
			
		||||
#ifdef HARDWARE_SCROLL_AVAILABLE // when display supports hardware scrolling -> only the content has to be updated
 | 
			
		||||
	// note: scrolling is enabled at screen change (display_selectStatusPage())
 | 
			
		||||
	// update text every iteration to prevent empty screen at start
 | 
			
		||||
	displayTextLine(&dev, currentLine, false, false, "IDLE since:");
 | 
			
		||||
	displayTextLine(&dev, currentLine + 1, false, false, "%.1fh, B:%02.0f%%",
 | 
			
		||||
					(float)objects->control->getInactivityDurationMs() / 1000 / 60 / 60,
 | 
			
		||||
					getBatteryPercent());
 | 
			
		||||
	// note: scrolling is disabled at screen change (display_selectStatusPage())
 | 
			
		||||
#else // custom implementation to scroll the text 1 character to the right every iteration (also wraps over the end to beginning)
 | 
			
		||||
	static int offset = DISPLAY_HORIZONTAL_CHARACTER_COUNT;
 | 
			
		||||
	char buf1[64], buf2[64];
 | 
			
		||||
	// scroll text left to right (taken window of the string moves to the left => offset 16->0, 16->0 ...)
 | 
			
		||||
	offset -= 1;
 | 
			
		||||
	if (offset < 0)
 | 
			
		||||
		offset = DISPLAY_HORIZONTAL_CHARACTER_COUNT - 1; // 0 = no crop -> start over with crop
 | 
			
		||||
	// note: these strings have to be symetrical and 2x display character count long
 | 
			
		||||
	snprintf(buf1, 64, "IDLE since:     IDLE since:     ");
 | 
			
		||||
	snprintf(buf2, 64, "%.1fh, B:%02.0f%%     %.1fh, B:%02.0f%%     ",
 | 
			
		||||
			 (float)objects->control->getInactivityDurationMs() / 1000 / 60 / 60,
 | 
			
		||||
			 getBatteryPercent(),
 | 
			
		||||
			 (float)objects->control->getInactivityDurationMs() / 1000 / 60 / 60,
 | 
			
		||||
			 getBatteryPercent());
 | 
			
		||||
	// print strings on display while limiting to certain window (ignore certain count of characters at start)
 | 
			
		||||
	displayTextLine(&dev, currentLine, false, false, "%s", buf1 + offset);
 | 
			
		||||
	displayTextLine(&dev, currentLine + 1, false, false, "%s", buf2 + offset);
 | 
			
		||||
#endif
 | 
			
		||||
	//-- handle line rotation --
 | 
			
		||||
	// to not block the display task for several seconds returning every e.g. 500ms here
 | 
			
		||||
	// -> ensures detection of activity (exit condition) in task loop is handled regularly
 | 
			
		||||
	if (msPassed > STATUS_SCREEN_SCREENSAVER_DELAY_NEXT_LINE_MS) // switch to next line is due
 | 
			
		||||
	{
 | 
			
		||||
		msPassed = 0; // rest seconds count
 | 
			
		||||
		// increment / rotate to next line
 | 
			
		||||
		if (++currentLine >= DISPLAY_VERTICAL_LINE_COUNT - 1) // rotate to next line
 | 
			
		||||
			currentLine = 0;
 | 
			
		||||
		lineChanging = true; // clear screen in next run
 | 
			
		||||
	}
 | 
			
		||||
	//-- wait update interval --
 | 
			
		||||
	// wait and increment passed time after each run
 | 
			
		||||
	vTaskDelay(STATUS_SCREEN_SCREENSAVER_UPDATE_INTERVAL / portTICK_PERIOD_MS);
 | 
			
		||||
	msPassed += STATUS_SCREEN_SCREENSAVER_UPDATE_INTERVAL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//########################
 | 
			
		||||
//#### showStartupMsg ####
 | 
			
		||||
//########################
 | 
			
		||||
@ -353,16 +439,44 @@ void showStartupMsg(){
 | 
			
		||||
//============================
 | 
			
		||||
//===== selectStatusPage =====
 | 
			
		||||
//============================
 | 
			
		||||
void display_selectStatusPage(displayStatusPage_t newStatusPage){
 | 
			
		||||
void display_selectStatusPage(displayStatusPage_t newStatusPage)
 | 
			
		||||
{
 | 
			
		||||
	//-- run commands when switching FROM certain mode --
 | 
			
		||||
	switch (selectedStatusPage)
 | 
			
		||||
	{
 | 
			
		||||
#ifdef HARDWARE_SCROLL_AVAILABLE
 | 
			
		||||
	case STATUS_SCREEN_SCREENSAVER:
 | 
			
		||||
		ssd1306_hardware_scroll(&dev, SCROLL_STOP); // disable scrolling when exiting screensaver
 | 
			
		||||
		break;
 | 
			
		||||
#endif
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ESP_LOGW(TAG, "switching statusPage from %d to %d", (int)selectedStatusPage, (int)newStatusPage);
 | 
			
		||||
	selectedStatusPage = newStatusPage;
 | 
			
		||||
 | 
			
		||||
	//-- run commands when switching TO certain mode --
 | 
			
		||||
	switch (selectedStatusPage)
 | 
			
		||||
	{
 | 
			
		||||
	case STATUS_SCREEN_SCREENSAVER:
 | 
			
		||||
		ssd1306_clear_screen(&dev, false); // clear screen when switching
 | 
			
		||||
#ifdef HARDWARE_SCROLL_AVAILABLE
 | 
			
		||||
		ssd1306_hardware_scroll(&dev, SCROLL_RIGHT);
 | 
			
		||||
#endif
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//============================
 | 
			
		||||
//======= display task =======
 | 
			
		||||
//============================
 | 
			
		||||
// TODO: separate task for each loop?
 | 
			
		||||
 | 
			
		||||
void display_task(void *pvParameters)
 | 
			
		||||
{
 | 
			
		||||
	ESP_LOGW(TAG, "Initializing display and starting handle loop");
 | 
			
		||||
@ -403,6 +517,47 @@ void display_task(void *pvParameters)
 | 
			
		||||
			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;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// TODO add pages and menus
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ extern "C" {
 | 
			
		||||
 | 
			
		||||
// configuration for initializing display (passed to task as well)
 | 
			
		||||
typedef struct display_config_t {
 | 
			
		||||
    // initialization
 | 
			
		||||
    gpio_num_t gpio_scl;
 | 
			
		||||
    gpio_num_t gpio_sda;
 | 
			
		||||
    int gpio_reset; // negative number means reset pin is not connected or not used
 | 
			
		||||
@ -29,7 +30,11 @@ typedef struct display_config_t {
 | 
			
		||||
    int height;
 | 
			
		||||
    int offsetX;
 | 
			
		||||
    bool flip;
 | 
			
		||||
    int contrast;
 | 
			
		||||
    // display-task
 | 
			
		||||
    int contrastNormal;
 | 
			
		||||
    int contrastReduced;
 | 
			
		||||
    uint32_t timeoutReduceContrastMs;
 | 
			
		||||
    uint32_t timeoutSwitchToScreensaverMs;
 | 
			
		||||
} display_config_t;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -49,11 +54,14 @@ typedef struct 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} displayStatusPage_t;
 | 
			
		||||
typedef enum displayStatusPage_t {STATUS_SCREEN_OVERVIEW=0, STATUS_SCREEN_SPEED, STATUS_SCREEN_JOYSTICK, STATUS_SCREEN_MOTORS, STATUS_SCREEN_SCREENSAVER} displayStatusPage_t;
 | 
			
		||||
 | 
			
		||||
// get precise battery voltage (using lookup table)
 | 
			
		||||
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
 | 
			
		||||
void display_selectStatusPage(displayStatusPage_t newStatusPage);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,6 @@ extern "C"
 | 
			
		||||
#include "motorctl.hpp"
 | 
			
		||||
 | 
			
		||||
//folder single_board
 | 
			
		||||
#include "config.hpp"
 | 
			
		||||
#include "control.hpp" 
 | 
			
		||||
#include "button.hpp"
 | 
			
		||||
#include "display.hpp"
 | 
			
		||||
@ -108,7 +107,6 @@ nvs_handle_t nvsHandle;
 | 
			
		||||
//=================================
 | 
			
		||||
//initialize spi flash filesystem (used for webserver)
 | 
			
		||||
void init_spiffs(){
 | 
			
		||||
    ESP_LOGW(TAG, "initializing spiffs...");
 | 
			
		||||
    esp_vfs_spiffs_conf_t esp_vfs_spiffs_conf = {
 | 
			
		||||
        .base_path = "/spiffs",
 | 
			
		||||
        .partition_label = NULL,
 | 
			
		||||
@ -195,35 +193,29 @@ extern "C" void app_main(void) {
 | 
			
		||||
	gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);
 | 
			
		||||
	gpio_set_level(GPIO_NUM_17, 1);                                                      
 | 
			
		||||
 | 
			
		||||
	//--- initialize nvs-flash and netif (needed for wifi) ---
 | 
			
		||||
	ESP_LOGW(TAG,"initializing wifi...");
 | 
			
		||||
	wifi_initNvs_initNetif();
 | 
			
		||||
	//--- initialize nvs-flash and netif ---
 | 
			
		||||
	ESP_LOGW(TAG,"initializing NVS...");
 | 
			
		||||
	wifi_initNvs(); //needed for wifi and persistent config variables
 | 
			
		||||
	ESP_LOGW(TAG,"initializing NETIF...");
 | 
			
		||||
	wifi_initNetif(); // needed for wifi
 | 
			
		||||
 | 
			
		||||
	//--- initialize spiffs ---
 | 
			
		||||
	init_spiffs();
 | 
			
		||||
    ESP_LOGW(TAG, "initializing SPIFFS...");
 | 
			
		||||
	init_spiffs(); // used by httpd server
 | 
			
		||||
 | 
			
		||||
	//--- initialize and start wifi ---
 | 
			
		||||
	ESP_LOGW(TAG,"starting wifi...");
 | 
			
		||||
	//wifi_init_client(); //connect to existing wifi
 | 
			
		||||
	wifi_init_ap(); //start access point
 | 
			
		||||
	ESP_LOGD(TAG,"done starting wifi");
 | 
			
		||||
	// Note: now started only when switching to HTTP mode in control.cpp
 | 
			
		||||
	// ESP_LOGW(TAG,"starting wifi...");
 | 
			
		||||
	// wifi_start_client(); //connect to existing wifi (dropped)
 | 
			
		||||
	// wifi_start_ap(); //start access point
 | 
			
		||||
 | 
			
		||||
	//--- initialize encoder ---
 | 
			
		||||
	const QueueHandle_t encoderQueue = encoder_init(&encoder_config);
 | 
			
		||||
 | 
			
		||||
	//--- initialize nvs-flash ---  (for persistant config values)
 | 
			
		||||
	ESP_LOGW(TAG, "initializing nvs-flash...");
 | 
			
		||||
	esp_err_t err = nvs_flash_init();
 | 
			
		||||
	if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
 | 
			
		||||
	{
 | 
			
		||||
		ESP_LOGE(TAG, "NVS truncated -> deleting flash");
 | 
			
		||||
		// Retry nvs_flash_init
 | 
			
		||||
		ESP_ERROR_CHECK(nvs_flash_erase());
 | 
			
		||||
		err = nvs_flash_init();
 | 
			
		||||
	}
 | 
			
		||||
	ESP_ERROR_CHECK(err);
 | 
			
		||||
	//--- open nvs-flash ---
 | 
			
		||||
	err = nvs_open("storage", NVS_READWRITE, &nvsHandle);
 | 
			
		||||
	// note: nvs already initialized in wifi_initNvs()
 | 
			
		||||
    ESP_LOGW(TAG, "opening NVS-handle...");
 | 
			
		||||
	esp_err_t err = nvs_open("storage", NVS_READWRITE, &nvsHandle); // this handle is passed to all tasks for accessing nvs
 | 
			
		||||
	if (err != ESP_OK)
 | 
			
		||||
		ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@ extern "C"{
 | 
			
		||||
 | 
			
		||||
#include "menu.hpp"
 | 
			
		||||
#include "encoder.hpp"
 | 
			
		||||
#include "config.hpp"
 | 
			
		||||
#include "motorctl.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -105,7 +104,7 @@ void item_calibrateJoystick_action(display_task_parameters_t *objects, SSD1306_t
 | 
			
		||||
        // save and next when button clicked, exit when long pressed
 | 
			
		||||
        if (xQueueReceive(objects->encoderQueue, &event, CALIBRATE_JOYSTICK_UPDATE_INTERVAL / portTICK_PERIOD_MS))
 | 
			
		||||
        {
 | 
			
		||||
            objects->control->resetTimeout();
 | 
			
		||||
            objects->control->resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
            switch (event.type)
 | 
			
		||||
            {
 | 
			
		||||
            case RE_ET_BTN_CLICKED:
 | 
			
		||||
@ -215,7 +214,7 @@ void item_debugJoystick_action(display_task_parameters_t * objects, SSD1306_t *
 | 
			
		||||
        // exit when button pressed
 | 
			
		||||
        if (xQueueReceive(objects->encoderQueue, &event, DEBUG_JOYSTICK_UPDATE_INTERVAL / portTICK_PERIOD_MS))
 | 
			
		||||
        {
 | 
			
		||||
            objects->control->resetTimeout();
 | 
			
		||||
            objects->control->resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
            switch (event.type)
 | 
			
		||||
            {
 | 
			
		||||
            case RE_ET_BTN_CLICKED:
 | 
			
		||||
@ -700,7 +699,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
        {
 | 
			
		||||
            // reset menu- and control-timeout on any encoder event
 | 
			
		||||
            lastActivity = esp_log_timestamp();
 | 
			
		||||
            objects->control->resetTimeout();
 | 
			
		||||
            objects->control->resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
            switch (event.type)
 | 
			
		||||
            {
 | 
			
		||||
            case RE_ET_CHANGED:
 | 
			
		||||
@ -771,7 +770,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
        // wait for encoder event
 | 
			
		||||
        if (xQueueReceive(objects->encoderQueue, &event, QUEUE_TIMEOUT / portTICK_PERIOD_MS))
 | 
			
		||||
        {
 | 
			
		||||
            objects->control->resetTimeout();
 | 
			
		||||
            objects->control->resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
            switch (event.type)
 | 
			
		||||
            {
 | 
			
		||||
            case RE_ET_CHANGED:
 | 
			
		||||
@ -811,7 +810,7 @@ void handleMenu(display_task_parameters_t * objects, SSD1306_t *display)
 | 
			
		||||
            }
 | 
			
		||||
            // reset menu- and control-timeout on any encoder event
 | 
			
		||||
            lastActivity = esp_log_timestamp();
 | 
			
		||||
            objects->control->resetTimeout();
 | 
			
		||||
            objects->control->resetTimeout(); // user input -> reset switch to IDLE timeout
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -21,26 +21,45 @@ static const char *TAG = "wifi";
 | 
			
		||||
static esp_event_handler_instance_t instance_any_id;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//============================================
 | 
			
		||||
//============ init nvs and netif ============
 | 
			
		||||
//============================================
 | 
			
		||||
//initialize nvs-flash and netif (needed for both AP and CLIENT)
 | 
			
		||||
//##########################################
 | 
			
		||||
//############ common functions ############
 | 
			
		||||
//##########################################
 | 
			
		||||
 | 
			
		||||
//============================
 | 
			
		||||
//========= init nvs =========
 | 
			
		||||
//============================
 | 
			
		||||
//initialize nvs-flash (needed for both AP and CLIENT)
 | 
			
		||||
//has to be run once at startup 
 | 
			
		||||
void wifi_initNvs_initNetif(){
 | 
			
		||||
void wifi_initNvs(){
 | 
			
		||||
    //Initialize NVS (needed for wifi)
 | 
			
		||||
    esp_err_t ret = nvs_flash_init();
 | 
			
		||||
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
 | 
			
		||||
	esp_err_t err = nvs_flash_init();
 | 
			
		||||
	if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
 | 
			
		||||
	{
 | 
			
		||||
		ESP_LOGE(TAG, "NVS truncated -> deleting flash");
 | 
			
		||||
		// Retry nvs_flash_init
 | 
			
		||||
		ESP_ERROR_CHECK(nvs_flash_erase());
 | 
			
		||||
        ret = nvs_flash_init();
 | 
			
		||||
		err = nvs_flash_init();
 | 
			
		||||
	}
 | 
			
		||||
	ESP_ERROR_CHECK(err);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==============================
 | 
			
		||||
//========= init netif =========
 | 
			
		||||
//==============================
 | 
			
		||||
//initialize netif (needed for both AP and CLIENT)
 | 
			
		||||
//has to be run once at startup 
 | 
			
		||||
void wifi_initNetif(){
 | 
			
		||||
    ESP_ERROR_CHECK(esp_netif_init());
 | 
			
		||||
    ESP_ERROR_CHECK(esp_event_loop_create_default());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//===========================================
 | 
			
		||||
//============ init access point ============
 | 
			
		||||
//===========================================
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//############################################
 | 
			
		||||
//############### access point ###############
 | 
			
		||||
//############################################
 | 
			
		||||
 | 
			
		||||
//--------------------------------------------
 | 
			
		||||
//------ configuration / declarations --------
 | 
			
		||||
@ -66,10 +85,12 @@ static void wifi_event_handler_ap(void* arg, esp_event_base_t event_base,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//-----------------------
 | 
			
		||||
//------ init ap --------
 | 
			
		||||
//-----------------------
 | 
			
		||||
void wifi_init_ap(void)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//========================
 | 
			
		||||
//====== start AP ========
 | 
			
		||||
//========================
 | 
			
		||||
void wifi_start_ap(void)
 | 
			
		||||
{
 | 
			
		||||
    ap = esp_netif_create_default_wifi_ap();
 | 
			
		||||
 | 
			
		||||
@ -107,9 +128,9 @@ void wifi_init_ap(void)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//=============================
 | 
			
		||||
//========= deinit AP =========
 | 
			
		||||
//========== stop AP ==========
 | 
			
		||||
//=============================
 | 
			
		||||
void wifi_deinit_ap(void)
 | 
			
		||||
void wifi_stop_ap(void)
 | 
			
		||||
{
 | 
			
		||||
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
 | 
			
		||||
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
 | 
			
		||||
@ -123,9 +144,9 @@ void wifi_deinit_ap(void)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//===========================================
 | 
			
		||||
//=============== init client ===============
 | 
			
		||||
//===========================================
 | 
			
		||||
//##########################################
 | 
			
		||||
//################# client #################
 | 
			
		||||
//##########################################
 | 
			
		||||
 | 
			
		||||
//--------------------------------------------
 | 
			
		||||
//------ configuration / declarations --------
 | 
			
		||||
@ -168,10 +189,13 @@ static void event_handler(void* arg, esp_event_base_t event_base,
 | 
			
		||||
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
//---------------------------
 | 
			
		||||
//------ init client --------
 | 
			
		||||
//---------------------------
 | 
			
		||||
void wifi_init_client(void)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//===========================
 | 
			
		||||
//====== init client ========
 | 
			
		||||
//===========================
 | 
			
		||||
void wifi_start_client(void)
 | 
			
		||||
{
 | 
			
		||||
    s_wifi_event_group = xEventGroupCreate();
 | 
			
		||||
    sta = esp_netif_create_default_wifi_sta();
 | 
			
		||||
@ -249,10 +273,10 @@ void wifi_init_client(void)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//=================================
 | 
			
		||||
//========= deinit client =========
 | 
			
		||||
//=================================
 | 
			
		||||
void wifi_deinit_client(void)
 | 
			
		||||
//===============================
 | 
			
		||||
//========= stop client =========
 | 
			
		||||
//===============================
 | 
			
		||||
void wifi_stop_client(void)
 | 
			
		||||
{
 | 
			
		||||
    /* The event will not be processed after unregister */
 | 
			
		||||
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
 | 
			
		||||
 | 
			
		||||
@ -3,20 +3,20 @@
 | 
			
		||||
//TODO: currently wifi names and passwords are configured in wifi.c -> move this to config?
 | 
			
		||||
 | 
			
		||||
//initialize nvs-flash and netif (needed for both AP and CLIENT)
 | 
			
		||||
//has to be run once at startup 
 | 
			
		||||
//Note: this cant be put in wifi_init functions because this may not be in deinit functions
 | 
			
		||||
void wifi_initNvs_initNetif();
 | 
			
		||||
//both functions have to be run once at startup 
 | 
			
		||||
void wifi_initNvs();
 | 
			
		||||
void wifi_initNetif();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//function to start an access point
 | 
			
		||||
void wifi_init_ap(void);
 | 
			
		||||
//function to disable/deinit access point
 | 
			
		||||
void wifi_deinit_ap(void);
 | 
			
		||||
//function to start an access point (config in wifi.c)
 | 
			
		||||
void wifi_start_ap(void);
 | 
			
		||||
//function to disable/stop access point
 | 
			
		||||
void wifi_stop_ap(void);
 | 
			
		||||
 | 
			
		||||
//function to connect to existing wifi network
 | 
			
		||||
void wifi_init_client(void);
 | 
			
		||||
//function to connect to existing wifi network (config in wifi.c)
 | 
			
		||||
void wifi_start_client(void);
 | 
			
		||||
//function to disable/deinit client
 | 
			
		||||
void wifi_deinit_client(void);
 | 
			
		||||
void wifi_stop_client(void);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user