From 977569d1fbeb12c651e28acdfa1b6b82bce31b03 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Fri, 5 Jul 2024 15:57:30 +0200 Subject: [PATCH] PID UI (cherry picked from commit 8e030f87a7c187fa4f03205d2520e4f9b651fc37) --- data/config.html | 3 +- include/YaSolRDefines.h | 13 +- include/YaSolRWebsite.h | 35 +++- include/i18n/en.h | 38 ++--- include/i18n/fr.h | 38 ++--- src/Website.cpp | 344 +++++++++++++++++++++++++++++----------- src/init/Boot.cpp | 5 +- src/init/Debug.cpp | 2 +- src/init/Events.cpp | 1 + src/init/HTTP.cpp | 2 +- src/main.cpp | 2 +- src/tasks/Dashboard.cpp | 4 +- src/tasks/Router.cpp | 47 ++---- 13 files changed, 360 insertions(+), 174 deletions(-) diff --git a/data/config.html b/data/config.html index cf69e25..714aed4 100644 --- a/data/config.html +++ b/data/config.html @@ -166,9 +166,10 @@

YaSolR Configuration

relay2_type: ["Relay 2 Type", "select", "NO,NC"], "PID Calibration": "TITLE", + pid_view_enable: ["View PID Data", "switch"], pid_pmode: ["Proportional Mode (1: on Error, 2: on Input, 3: Both)", "select", "1,2,3"], pid_dmode: ["Derivative Mode (1: on Error, 2: on Input)", "select", "1,2"], - pid_icmode: ["Integral Correction Mode (0: Off, 1: Clamp, 2: Anti-windup)", "select", "0,1,2"], + pid_icmode: ["Integral Correction (0: Off, 1: Clamp, 2: Anti-windup)", "select", "0,1,2"], pid_setpoint: ["Setpoint (Target Grid Power)", "float"], pid_kp: ["Kp", "float"], pid_ki: ["Ki", "float"], diff --git a/include/YaSolRDefines.h b/include/YaSolRDefines.h index 516ff4c..e91af92 100644 --- a/include/YaSolRDefines.h +++ b/include/YaSolRDefines.h @@ -55,6 +55,14 @@ #define YASOLR_SERIAL_BAUDRATE 115200 #define YASOLR_WEEK_DAYS "sun,mon,tue,wed,thu,fri,sat" #define YASOLR_GRAPH_POINTS 120 +#define YASOLR_PID_P_MODE_1 "1: on Error" +#define YASOLR_PID_P_MODE_2 "2: on Input" +#define YASOLR_PID_P_MODE_3 "3: Both" +#define YASOLR_PID_D_MODE_1 YASOLR_PID_P_MODE_1 +#define YASOLR_PID_D_MODE_2 YASOLR_PID_P_MODE_2 +#define YASOLR_PID_IC_MODE_0 "0: Off" +#define YASOLR_PID_IC_MODE_1 "1: Clamp" +#define YASOLR_PID_IC_MODE_2 "2: Anti-windup" // UDP communication @@ -87,6 +95,7 @@ #define KEY_ENABLE_OUTPUT2_DS18 "o2_ds18_enable" #define KEY_ENABLE_OUTPUT2_PZEM "o2_pzem_enable" #define KEY_ENABLE_OUTPUT2_RELAY "o2_relay_enable" +#define KEY_ENABLE_PID_VIEW "pid_view_enable" #define KEY_ENABLE_RELAY1 "relay1_enable" #define KEY_ENABLE_RELAY2 "relay2_enable" #define KEY_ENABLE_ZCD "zcd_enable" @@ -130,15 +139,15 @@ #define KEY_OUTPUT2_TIME_START "o2_time_start" #define KEY_OUTPUT2_TIME_STOP "o2_time_stop" #define KEY_OUTPUT2_TIME_STOP "o2_time_stop" +#define KEY_PID_P_MODE "pid_pmode" #define KEY_PID_D_MODE "pid_dmode" #define KEY_PID_IC_MODE "pid_icmode" +#define KEY_PID_SETPOINT "pid_setpoint" #define KEY_PID_KD "pid_kd" #define KEY_PID_KI "pid_ki" #define KEY_PID_KP "pid_kp" #define KEY_PID_OUT_MAX "pid_out_max" #define KEY_PID_OUT_MIN "pid_out_min" -#define KEY_PID_P_MODE "pid_pmode" -#define KEY_PID_SETPOINT "pid_setpoint" #define KEY_RELAY1_LOAD "relay1_load" #define KEY_RELAY1_TYPE "relay1_type" #define KEY_RELAY2_LOAD "relay2_load" diff --git a/include/YaSolRWebsite.h b/include/YaSolRWebsite.h index 858a889..778267f 100644 --- a/include/YaSolRWebsite.h +++ b/include/YaSolRWebsite.h @@ -25,8 +25,12 @@ namespace YaSolR { void initCards(); void updateCards(); void updateCharts(); + void updatePID(); + void resetPID(); private: + int _historyX[YASOLR_GRAPH_POINTS] = {0}; + // statistics Statistic _appName = Statistic(&dashboard, YASOLR_LBL_001); Statistic _appModel = Statistic(&dashboard, YASOLR_LBL_002); @@ -87,7 +91,6 @@ namespace YaSolR { Card _gridPower = Card(&dashboard, ENERGY_CARD, YASOLR_LBL_044, "W"); Card _routerDS18State = Card(&dashboard, TEMPERATURE_CARD, YASOLR_LBL_045, "°C"); - int _historyX[YASOLR_GRAPH_POINTS] = {0}; int _gridPowerHistoryY[YASOLR_GRAPH_POINTS] = {0}; int _routedPowerHistoryY[YASOLR_GRAPH_POINTS] = {0}; int _routerTHDiHistoryY[YASOLR_GRAPH_POINTS] = {0}; @@ -245,11 +248,41 @@ namespace YaSolR { Card _output2RelayType = Card(&dashboard, DROPDOWN_CARD, YASOLR_LBL_150); Card _relay1Type = Card(&dashboard, DROPDOWN_CARD, YASOLR_LBL_151); Card _relay2Type = Card(&dashboard, DROPDOWN_CARD, YASOLR_LBL_152); + + Tab _pidTab = Tab(&dashboard, "\u2699 " YASOLR_LBL_159); + Card _pidView = Card(&dashboard, BUTTON_CARD, YASOLR_LBL_169); + Card _pidPMode = Card(&dashboard, DROPDOWN_CARD, YASOLR_LBL_160); + Card _pidDMode = Card(&dashboard, DROPDOWN_CARD, YASOLR_LBL_161); + Card _pidICMode = Card(&dashboard, DROPDOWN_CARD, YASOLR_LBL_162); + Card _pidSetpoint = Card(&dashboard, TEXT_INPUT_CARD, YASOLR_LBL_163); + Card _pidKp = Card(&dashboard, TEXT_INPUT_CARD, YASOLR_LBL_166); + Card _pidKi = Card(&dashboard, TEXT_INPUT_CARD, YASOLR_LBL_167); + Card _pidKd = Card(&dashboard, TEXT_INPUT_CARD, YASOLR_LBL_168); + Card _pidOutMin = Card(&dashboard, TEXT_INPUT_CARD, YASOLR_LBL_164); + Card _pidOutMax = Card(&dashboard, TEXT_INPUT_CARD, YASOLR_LBL_165); + Card _pidReset = Card(&dashboard, PUSH_BUTTON_CARD, YASOLR_LBL_177); + + // input,output,error,pTerm,iTerm,dTerm,sum + int _pidInputHistoryY[YASOLR_GRAPH_POINTS] = {0}; + int _pidOutputHistoryY[YASOLR_GRAPH_POINTS] = {0}; + int _pidErrorHistoryY[YASOLR_GRAPH_POINTS] = {0}; + int _pidPTermHistoryY[YASOLR_GRAPH_POINTS] = {0}; + int _pidITermHistoryY[YASOLR_GRAPH_POINTS] = {0}; + int _pidDTermHistoryY[YASOLR_GRAPH_POINTS] = {0}; + int _pidSumHistoryY[YASOLR_GRAPH_POINTS] = {0}; + Chart _pidInputHistory = Chart(&dashboard, LINE_CHART, YASOLR_LBL_170); + Chart _pidOutputHistory = Chart(&dashboard, LINE_CHART, YASOLR_LBL_171); + Chart _pidErrorHistory = Chart(&dashboard, LINE_CHART, YASOLR_LBL_172); + Chart _pidSumHistory = Chart(&dashboard, BAR_CHART, YASOLR_LBL_173); + Chart _pidPTermHistory = Chart(&dashboard, LINE_CHART, YASOLR_LBL_174); + Chart _pidITermHistory = Chart(&dashboard, LINE_CHART, YASOLR_LBL_175); + Chart _pidDTermHistory = Chart(&dashboard, LINE_CHART, YASOLR_LBL_176); #endif private: void _boolConfig(Card& card, const char* key); void _daysConfig(Card& card, const char* key); + void _floatConfig(Card& card, const char* key); void _numConfig(Card& card, const char* key); void _pinConfig(Card& card, const char* key); void _passwordConfig(Card& card, const char* key); diff --git a/include/i18n/en.h b/include/i18n/en.h index 9f08d2c..40d0f33 100644 --- a/include/i18n/en.h +++ b/include/i18n/en.h @@ -162,25 +162,25 @@ #define YASOLR_LBL_156 "I/O" #define YASOLR_LBL_157 "JSY Remote UDP: Message Rate" #define YASOLR_LBL_158 "Grid Excess Remaining" -#define YASOLR_LBL_159 -#define YASOLR_LBL_160 -#define YASOLR_LBL_161 -#define YASOLR_LBL_162 -#define YASOLR_LBL_163 -#define YASOLR_LBL_164 -#define YASOLR_LBL_165 -#define YASOLR_LBL_166 -#define YASOLR_LBL_167 -#define YASOLR_LBL_168 -#define YASOLR_LBL_169 -#define YASOLR_LBL_170 -#define YASOLR_LBL_171 -#define YASOLR_LBL_172 -#define YASOLR_LBL_173 -#define YASOLR_LBL_174 -#define YASOLR_LBL_175 -#define YASOLR_LBL_176 -#define YASOLR_LBL_177 +#define YASOLR_LBL_159 "PID Controller" +#define YASOLR_LBL_160 "Proportional Mode" +#define YASOLR_LBL_161 "Derivative Mode" +#define YASOLR_LBL_162 "Integral Correction" +#define YASOLR_LBL_164 "Output Min (W)" +#define YASOLR_LBL_165 "Output Max (W)" +#define YASOLR_LBL_163 "Setpoint (Target Grid Power in W)" +#define YASOLR_LBL_166 "Kp" +#define YASOLR_LBL_167 "Ki" +#define YASOLR_LBL_168 "Kd" +#define YASOLR_LBL_169 "Real-time PID Data" +#define YASOLR_LBL_170 "Input (Grid Power, W)" +#define YASOLR_LBL_171 "Output (Power to route, W)" +#define YASOLR_LBL_172 "Error (W)" +#define YASOLR_LBL_173 "Sum (W)" +#define YASOLR_LBL_174 "P Term (W)" +#define YASOLR_LBL_175 "I Term (W)" +#define YASOLR_LBL_176 "D Term (W)" +#define YASOLR_LBL_177 "Chart Reset" #define YASOLR_LBL_178 #define YASOLR_LBL_179 #define YASOLR_LBL_180 diff --git a/include/i18n/fr.h b/include/i18n/fr.h index 21ee8cd..e966a68 100644 --- a/include/i18n/fr.h +++ b/include/i18n/fr.h @@ -162,25 +162,25 @@ #define YASOLR_LBL_156 "E/S" #define YASOLR_LBL_157 "JSY Remote UDP: Débit" #define YASOLR_LBL_158 "Surplus réseau restant" -#define YASOLR_LBL_159 -#define YASOLR_LBL_160 -#define YASOLR_LBL_161 -#define YASOLR_LBL_162 -#define YASOLR_LBL_163 -#define YASOLR_LBL_164 -#define YASOLR_LBL_165 -#define YASOLR_LBL_166 -#define YASOLR_LBL_167 -#define YASOLR_LBL_168 -#define YASOLR_LBL_169 -#define YASOLR_LBL_170 -#define YASOLR_LBL_171 -#define YASOLR_LBL_172 -#define YASOLR_LBL_173 -#define YASOLR_LBL_174 -#define YASOLR_LBL_175 -#define YASOLR_LBL_176 -#define YASOLR_LBL_177 +#define YASOLR_LBL_159 "Contrôleur PID" +#define YASOLR_LBL_160 "Mode proportionnel" +#define YASOLR_LBL_161 "Mode dérivé" +#define YASOLR_LBL_162 "Correction d'Intégrale" +#define YASOLR_LBL_164 "Sortie Min (W)" +#define YASOLR_LBL_165 "Sortie Max (W)" +#define YASOLR_LBL_163 "Consigne (puissance cible du réseau en W)" +#define YASOLR_LBL_166 "Kp" +#define YASOLR_LBL_167 "Ki" +#define YASOLR_LBL_168 "Kd" +#define YASOLR_LBL_169 "Données PID temps-réel" +#define YASOLR_LBL_170 "Entrée (Puissance réseau, W)" +#define YASOLR_LBL_171 "Sortie (Puissance à router, W)" +#define YASOLR_LBL_172 "Erreur (W)" +#define YASOLR_LBL_173 "Somme (W)" +#define YASOLR_LBL_174 "Terme P (W)" +#define YASOLR_LBL_175 "Terme I (W)" +#define YASOLR_LBL_176 "Terme D (W)" +#define YASOLR_LBL_177 "Réinitialisation graphique" #define YASOLR_LBL_178 #define YASOLR_LBL_179 #define YASOLR_LBL_180 diff --git a/src/Website.cpp b/src/Website.cpp index 73e7d6e..a613c09 100644 --- a/src/Website.cpp +++ b/src/Website.cpp @@ -16,18 +16,37 @@ void YaSolR::WebsiteClass::initLayout() { for (int i = 0; i < YASOLR_GRAPH_POINTS; i++) _historyX[i] = i - YASOLR_GRAPH_POINTS; + // overview _gridPowerHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); _routedPowerHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); _routerTHDiHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + // PID + _pidInputHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + _pidOutputHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + _pidErrorHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + _pidPTermHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + _pidITermHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + _pidDTermHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + _pidSumHistory.updateX(_historyX, YASOLR_GRAPH_POINTS); + #ifdef APP_MODEL_PRO dashboard.setChartAnimations(false); - // graphs + // overview graphs _gridPowerHistory.setSize(chartSize); _routedPowerHistory.setSize(chartSize); _routerTHDiHistory.setSize(chartSize); + // pid graphs + _pidInputHistory.setSize(chartSize); + _pidOutputHistory.setSize(chartSize); + _pidPTermHistory.setSize(chartSize); + _pidITermHistory.setSize(chartSize); + _pidDTermHistory.setSize(chartSize); + _pidErrorHistory.setSize(chartSize); + _pidSumHistory.setSize(chartSize); + // output 1 (status) _output1State.setTab(&_output1Tab); _output1DS18State.setTab(&_output1Tab); @@ -145,6 +164,55 @@ void YaSolR::WebsiteClass::initLayout() { _reset.attachCallback([] PUSH_BUTTON_CARD_CB { resetTask.resume(); }); _restart.attachCallback([] PUSH_BUTTON_CARD_CB { restartTask.resume(); }); + // network (config) + _adminPwd.setTab(&_networkConfigTab); + _apMode.setTab(&_networkConfigTab); + _ntpServer.setTab(&_networkConfigTab); + _ntpSync.setTab(&_networkConfigTab); + _ntpTimezone.setTab(&_networkConfigTab); + _wifiPwd.setTab(&_networkConfigTab); + _wifiSSID.setTab(&_networkConfigTab); + + _boolConfig(_apMode, KEY_ENABLE_AP_MODE); + _passwordConfig(_adminPwd, KEY_ADMIN_PASSWORD); + _passwordConfig(_wifiPwd, KEY_WIFI_PASSWORD); + _textConfig(_ntpServer, KEY_NTP_SERVER); + _textConfig(_ntpTimezone, KEY_NTP_TIMEZONE); + _textConfig(_wifiSSID, KEY_WIFI_SSID); + + _ntpSync.attachCallback([](const char* value) { + const String str = String(value); + const size_t len = str.length(); + const timeval tv = {str.substring(0, len - 3).toInt(), str.substring(len - 3).toInt()}; + Mycila::NTP.sync(tv); + }); + + // mqtt (config) + _haDiscovery.setTab(&_mqttConfigTab); + _haDiscoveryTopic.setTab(&_mqttConfigTab); + _mqttGridPower.setTab(&_mqttConfigTab); + _mqttGridVoltage.setTab(&_mqttConfigTab); + _mqttPort.setTab(&_mqttConfigTab); + _mqttPublishInterval.setTab(&_mqttConfigTab); + _mqttPwd.setTab(&_mqttConfigTab); + _mqttSecured.setTab(&_mqttConfigTab); + _mqttServer.setTab(&_mqttConfigTab); + _mqttServerCert.setTab(&_mqttConfigTab); + _mqttTopic.setTab(&_mqttConfigTab); + _mqttUser.setTab(&_mqttConfigTab); + + _boolConfig(_haDiscovery, KEY_ENABLE_HA_DISCOVERY); + _boolConfig(_mqttSecured, KEY_MQTT_SECURED); + _numConfig(_mqttPort, KEY_MQTT_PORT); + _passwordConfig(_mqttPwd, KEY_MQTT_PASSWORD); + _sliderConfig(_mqttPublishInterval, KEY_MQTT_PUBLISH_INTERVAL); + _textConfig(_haDiscoveryTopic, KEY_HA_DISCOVERY_TOPIC); + _textConfig(_mqttGridPower, KEY_GRID_POWER_MQTT_TOPIC); + _textConfig(_mqttGridVoltage, KEY_GRID_VOLTAGE_MQTT_TOPIC); + _textConfig(_mqttServer, KEY_MQTT_SERVER); + _textConfig(_mqttTopic, KEY_MQTT_TOPIC); + _textConfig(_mqttUser, KEY_MQTT_USERNAME); + // GPIO (configuration) _pinDimmerO1.setTab(&_pinConfigTab); _pinDimmerO2.setTab(&_pinConfigTab); @@ -196,8 +264,6 @@ void YaSolR::WebsiteClass::initLayout() { _output1Relay.setTab(&_hardwareEnableTab); _output1DS18.setTab(&_hardwareEnableTab); _output2Dimmer.setTab(&_hardwareEnableTab); - _output1ResistanceInput.setTab(&_hardwareConfigTab); - _output2ResistanceInput.setTab(&_hardwareConfigTab); _output2PZEM.setTab(&_hardwareEnableTab); _output2Relay.setTab(&_hardwareEnableTab); _output2DS18.setTab(&_hardwareEnableTab); @@ -234,6 +300,8 @@ void YaSolR::WebsiteClass::initLayout() { _output2RelayType.setTab(&_hardwareConfigTab); _relay1Type.setTab(&_hardwareConfigTab); _relay2Type.setTab(&_hardwareConfigTab); + _output1ResistanceInput.setTab(&_hardwareConfigTab); + _output2ResistanceInput.setTab(&_hardwareConfigTab); _numConfig(_gridFreq, KEY_GRID_FREQUENCY); _numConfig(_displayRotation, KEY_DISPLAY_ROTATION); @@ -251,54 +319,42 @@ void YaSolR::WebsiteClass::initLayout() { _output1PZEMSync.attachCallback([] PUSH_BUTTON_CARD_CB { pzemO1PairingTask.resume(); }); _output2PZEMSync.attachCallback([] PUSH_BUTTON_CARD_CB { pzemO2PairingTask.resume(); }); - // mqtt (config) - _haDiscovery.setTab(&_mqttConfigTab); - _haDiscoveryTopic.setTab(&_mqttConfigTab); - _mqttGridPower.setTab(&_mqttConfigTab); - _mqttGridVoltage.setTab(&_mqttConfigTab); - _mqttPort.setTab(&_mqttConfigTab); - _mqttPublishInterval.setTab(&_mqttConfigTab); - _mqttPwd.setTab(&_mqttConfigTab); - _mqttSecured.setTab(&_mqttConfigTab); - _mqttServer.setTab(&_mqttConfigTab); - _mqttServerCert.setTab(&_mqttConfigTab); - _mqttTopic.setTab(&_mqttConfigTab); - _mqttUser.setTab(&_mqttConfigTab); - - _boolConfig(_haDiscovery, KEY_ENABLE_HA_DISCOVERY); - _boolConfig(_mqttSecured, KEY_MQTT_SECURED); - _numConfig(_mqttPort, KEY_MQTT_PORT); - _passwordConfig(_mqttPwd, KEY_MQTT_PASSWORD); - _sliderConfig(_mqttPublishInterval, KEY_MQTT_PUBLISH_INTERVAL); - _textConfig(_haDiscoveryTopic, KEY_HA_DISCOVERY_TOPIC); - _textConfig(_mqttGridPower, KEY_GRID_POWER_MQTT_TOPIC); - _textConfig(_mqttGridVoltage, KEY_GRID_VOLTAGE_MQTT_TOPIC); - _textConfig(_mqttServer, KEY_MQTT_SERVER); - _textConfig(_mqttTopic, KEY_MQTT_TOPIC); - _textConfig(_mqttUser, KEY_MQTT_USERNAME); - - // network (config) - _adminPwd.setTab(&_networkConfigTab); - _apMode.setTab(&_networkConfigTab); - _ntpServer.setTab(&_networkConfigTab); - _ntpSync.setTab(&_networkConfigTab); - _ntpTimezone.setTab(&_networkConfigTab); - _wifiPwd.setTab(&_networkConfigTab); - _wifiSSID.setTab(&_networkConfigTab); - - _boolConfig(_apMode, KEY_ENABLE_AP_MODE); - _passwordConfig(_adminPwd, KEY_ADMIN_PASSWORD); - _passwordConfig(_wifiPwd, KEY_WIFI_PASSWORD); - _textConfig(_ntpServer, KEY_NTP_SERVER); - _textConfig(_ntpTimezone, KEY_NTP_TIMEZONE); - _textConfig(_wifiSSID, KEY_WIFI_SSID); - - _ntpSync.attachCallback([](const char* value) { - const String str = String(value); - const size_t len = str.length(); - const timeval tv = {str.substring(0, len - 3).toInt(), str.substring(len - 3).toInt()}; - Mycila::NTP.sync(tv); + // PID + _pidView.setTab(&_pidTab); + _pidPMode.setTab(&_pidTab); + _pidDMode.setTab(&_pidTab); + _pidICMode.setTab(&_pidTab); + _pidSetpoint.setTab(&_pidTab); + _pidKp.setTab(&_pidTab); + _pidKi.setTab(&_pidTab); + _pidKd.setTab(&_pidTab); + _pidOutMin.setTab(&_pidTab); + _pidOutMax.setTab(&_pidTab); + + _pidInputHistory.setTab(&_pidTab); + _pidOutputHistory.setTab(&_pidTab); + _pidErrorHistory.setTab(&_pidTab); + _pidSumHistory.setTab(&_pidTab); + _pidPTermHistory.setTab(&_pidTab); + _pidITermHistory.setTab(&_pidTab); + _pidDTermHistory.setTab(&_pidTab); + _pidReset.setTab(&_pidTab); + + _pidReset.attachCallback([this] PUSH_BUTTON_CARD_CB { + resetPID(); + updatePID(); }); + + _boolConfig(_pidView, KEY_ENABLE_PID_VIEW); + _numConfig(_pidPMode, KEY_PID_P_MODE); + _numConfig(_pidDMode, KEY_PID_D_MODE); + _numConfig(_pidICMode, KEY_PID_IC_MODE); + _numConfig(_pidSetpoint, KEY_PID_SETPOINT); + _floatConfig(_pidKp, KEY_PID_KP); + _floatConfig(_pidKi, KEY_PID_KI); + _floatConfig(_pidKd, KEY_PID_KD); + _numConfig(_pidOutMin, KEY_PID_OUT_MIN); + _numConfig(_pidOutMax, KEY_PID_OUT_MAX); #endif } @@ -326,12 +382,12 @@ void YaSolR::WebsiteClass::initCards() { #ifdef APP_MODEL_PRO // output 1 (control) - bool dimmer1Enabled = config.getBool(KEY_ENABLE_OUTPUT1_DIMMER); - bool output1RelayEnabled = config.getBool(KEY_ENABLE_OUTPUT1_RELAY); - bool bypass1Possible = dimmer1Enabled || output1RelayEnabled; - bool autoDimmerO1Activated = config.getBool(KEY_ENABLE_OUTPUT1_AUTO_DIMMER); - bool autoBypassActivated = config.getBool(KEY_ENABLE_OUTPUT1_AUTO_BYPASS); - bool output1DS18Enabled = config.getBool(KEY_ENABLE_OUTPUT1_DS18); + const bool dimmer1Enabled = config.getBool(KEY_ENABLE_OUTPUT1_DIMMER); + const bool output1RelayEnabled = config.getBool(KEY_ENABLE_OUTPUT1_RELAY); + const bool bypass1Possible = dimmer1Enabled || output1RelayEnabled; + const bool autoDimmerO1Activated = config.getBool(KEY_ENABLE_OUTPUT1_AUTO_DIMMER); + const bool autoBypassActivated = config.getBool(KEY_ENABLE_OUTPUT1_AUTO_BYPASS); + const bool output1DS18Enabled = config.getBool(KEY_ENABLE_OUTPUT1_DS18); _output1DimmerAuto.update(autoDimmerO1Activated); _output1DimmerRatio.update(static_cast(config.get(KEY_OUTPUT1_RESERVED_EXCESS).toInt())); @@ -369,12 +425,12 @@ void YaSolR::WebsiteClass::initCards() { _output1AutoStoptTemp.setDisplay(bypass1Possible && autoBypassActivated && output1DS18Enabled); // output 2 (control) - bool dimmer2Enabled = config.getBool(KEY_ENABLE_OUTPUT2_DIMMER); - bool output2RelayEnabled = config.getBool(KEY_ENABLE_OUTPUT2_RELAY); - bool bypass2Possible = dimmer2Enabled || output2RelayEnabled; - bool autoDimmerO2Activated = config.getBool(KEY_ENABLE_OUTPUT2_AUTO_DIMMER); - bool autoBypassO2Activated = config.getBool(KEY_ENABLE_OUTPUT2_AUTO_BYPASS); - bool output2DS18Enabled = config.getBool(KEY_ENABLE_OUTPUT2_DS18); + const bool dimmer2Enabled = config.getBool(KEY_ENABLE_OUTPUT2_DIMMER); + const bool output2RelayEnabled = config.getBool(KEY_ENABLE_OUTPUT2_RELAY); + const bool bypass2Possible = dimmer2Enabled || output2RelayEnabled; + const bool autoDimmerO2Activated = config.getBool(KEY_ENABLE_OUTPUT2_AUTO_DIMMER); + const bool autoBypassO2Activated = config.getBool(KEY_ENABLE_OUTPUT2_AUTO_BYPASS); + const bool output2DS18Enabled = config.getBool(KEY_ENABLE_OUTPUT2_DS18); _output2DimmerAuto.update(autoDimmerO2Activated); _output2DimmerRatio.update(static_cast(config.get(KEY_OUTPUT2_RESERVED_EXCESS).toInt())); @@ -432,6 +488,29 @@ void YaSolR::WebsiteClass::initCards() { _otaLink.update("/update"); _energyReset.setDisplay(config.getBool(KEY_ENABLE_JSY) || config.getBool(KEY_ENABLE_OUTPUT1_PZEM) || config.getBool(KEY_ENABLE_OUTPUT2_PZEM)); + // network (config) + _adminPwd.update(config.get(KEY_ADMIN_PASSWORD).isEmpty() ? "" : HIDDEN_PWD); + _apMode.update(config.getBool(KEY_ENABLE_AP_MODE)); + _ntpServer.update(config.get(KEY_NTP_SERVER)); + _ntpTimezone.update(config.get(KEY_NTP_TIMEZONE), "/timezones"); + _wifiPwd.update(config.get(KEY_WIFI_PASSWORD).isEmpty() ? "" : HIDDEN_PWD); + _wifiSSID.update(config.get(KEY_WIFI_SSID)); + + // mqtt (config) + _haDiscovery.update(config.getBool(KEY_ENABLE_HA_DISCOVERY)); + _haDiscoveryTopic.update(config.get(KEY_HA_DISCOVERY_TOPIC)); + _mqttGridPower.update(config.get(KEY_GRID_POWER_MQTT_TOPIC)); + _mqttGridVoltage.update(config.get(KEY_GRID_VOLTAGE_MQTT_TOPIC)); + _mqttPort.update(config.get(KEY_MQTT_PORT)); + _mqttPublishInterval.update(config.get(KEY_MQTT_PUBLISH_INTERVAL)); + _mqttPwd.update(config.get(KEY_MQTT_PASSWORD).isEmpty() ? "" : HIDDEN_PWD); + _mqttSecured.update(config.getBool(KEY_MQTT_SECURED)); + _mqttServer.update(config.get(KEY_MQTT_SERVER)); + _mqttServerCert.update("/api/config/mqttServerCertificate"); + _mqttTopic.update(config.get(KEY_MQTT_TOPIC)); + _mqttUser.update(config.get(KEY_MQTT_USERNAME)); + _mqttConfigTab.setDisplay(config.getBool(KEY_ENABLE_MQTT)); + // GPIO std::map pinout = {}; @@ -488,28 +567,62 @@ void YaSolR::WebsiteClass::initCards() { _relay1Type.setDisplay(config.getBool(KEY_ENABLE_RELAY1)); _relay2Type.setDisplay(config.getBool(KEY_ENABLE_RELAY2)); - // mqtt (config) - _haDiscovery.update(config.getBool(KEY_ENABLE_HA_DISCOVERY)); - _haDiscoveryTopic.update(config.get(KEY_HA_DISCOVERY_TOPIC)); - _mqttGridPower.update(config.get(KEY_GRID_POWER_MQTT_TOPIC)); - _mqttGridVoltage.update(config.get(KEY_GRID_VOLTAGE_MQTT_TOPIC)); - _mqttPort.update(config.get(KEY_MQTT_PORT)); - _mqttPublishInterval.update(config.get(KEY_MQTT_PUBLISH_INTERVAL)); - _mqttPwd.update(config.get(KEY_MQTT_PASSWORD).isEmpty() ? "" : HIDDEN_PWD); - _mqttSecured.update(config.getBool(KEY_MQTT_SECURED)); - _mqttServer.update(config.get(KEY_MQTT_SERVER)); - _mqttServerCert.update("/api/config/mqttServerCertificate"); - _mqttTopic.update(config.get(KEY_MQTT_TOPIC)); - _mqttUser.update(config.get(KEY_MQTT_USERNAME)); - _mqttConfigTab.setDisplay(config.getBool(KEY_ENABLE_MQTT)); - - // network (config) - _adminPwd.update(config.get(KEY_ADMIN_PASSWORD).isEmpty() ? "" : HIDDEN_PWD); - _apMode.update(config.getBool(KEY_ENABLE_AP_MODE)); - _ntpServer.update(config.get(KEY_NTP_SERVER)); - _ntpTimezone.update(config.get(KEY_NTP_TIMEZONE), "/timezones"); - _wifiPwd.update(config.get(KEY_WIFI_PASSWORD).isEmpty() ? "" : HIDDEN_PWD); - _wifiSSID.update(config.get(KEY_WIFI_SSID)); + // PID + const bool pidViewEnabled = config.getBool(KEY_ENABLE_PID_VIEW); + _pidView.update(pidViewEnabled); + switch (config.get(KEY_PID_P_MODE).toInt()) { + case 1: + _pidPMode.update(YASOLR_PID_P_MODE_1, YASOLR_PID_P_MODE_1 "," YASOLR_PID_P_MODE_2 "," YASOLR_PID_P_MODE_3); + break; + case 2: + _pidPMode.update(YASOLR_PID_P_MODE_2, YASOLR_PID_P_MODE_1 "," YASOLR_PID_P_MODE_2 "," YASOLR_PID_P_MODE_3); + break; + case 3: + _pidPMode.update(YASOLR_PID_P_MODE_3, YASOLR_PID_P_MODE_1 "," YASOLR_PID_P_MODE_2 "," YASOLR_PID_P_MODE_3); + break; + default: + _pidPMode.update("", YASOLR_PID_P_MODE_1 "," YASOLR_PID_P_MODE_2 "," YASOLR_PID_P_MODE_3); + break; + } + switch (config.get(KEY_PID_D_MODE).toInt()) { + case 1: + _pidDMode.update(YASOLR_PID_D_MODE_1, YASOLR_PID_D_MODE_1 "," YASOLR_PID_D_MODE_2); + break; + case 2: + _pidDMode.update(YASOLR_PID_D_MODE_2, YASOLR_PID_D_MODE_1 "," YASOLR_PID_D_MODE_2); + break; + default: + _pidDMode.update("", YASOLR_PID_D_MODE_1 "," YASOLR_PID_D_MODE_2); + break; + } + switch (config.get(KEY_PID_IC_MODE).toInt()) { + case 0: + _pidICMode.update(YASOLR_PID_IC_MODE_0, YASOLR_PID_IC_MODE_0 "," YASOLR_PID_IC_MODE_1 "," YASOLR_PID_IC_MODE_2); + break; + case 1: + _pidICMode.update(YASOLR_PID_IC_MODE_1, YASOLR_PID_IC_MODE_0 "," YASOLR_PID_IC_MODE_1 "," YASOLR_PID_IC_MODE_2); + break; + case 2: + _pidICMode.update(YASOLR_PID_IC_MODE_2, YASOLR_PID_IC_MODE_0 "," YASOLR_PID_IC_MODE_1 "," YASOLR_PID_IC_MODE_2); + break; + default: + _pidICMode.update("", YASOLR_PID_IC_MODE_0 "," YASOLR_PID_IC_MODE_1 "," YASOLR_PID_IC_MODE_2); + break; + } + _pidSetpoint.update(config.get(KEY_PID_SETPOINT)); + _pidKp.update(config.get(KEY_PID_KP)); + _pidKi.update(config.get(KEY_PID_KI)); + _pidKd.update(config.get(KEY_PID_KD)); + _pidOutMin.update(config.get(KEY_PID_OUT_MIN)); + _pidOutMax.update(config.get(KEY_PID_OUT_MAX)); + + _pidInputHistory.setDisplay(pidViewEnabled); + _pidOutputHistory.setDisplay(pidViewEnabled); + _pidErrorHistory.setDisplay(pidViewEnabled); + _pidSumHistory.setDisplay(pidViewEnabled); + _pidPTermHistory.setDisplay(pidViewEnabled); + _pidITermHistory.setDisplay(pidViewEnabled); + _pidDTermHistory.setDisplay(pidViewEnabled); #endif } @@ -656,11 +769,10 @@ void YaSolR::WebsiteClass::updateCharts() { router.getMeasurements(routerMetrics); // shift array - for (size_t i = 0; i < YASOLR_GRAPH_POINTS - 1; i++) { - _gridPowerHistoryY[i] = _gridPowerHistoryY[i + 1]; - _routedPowerHistoryY[i] = _routedPowerHistoryY[i + 1]; - _routerTHDiHistoryY[i] = _routerTHDiHistoryY[i + 1]; - } + constexpr size_t shift = sizeof(_gridPowerHistoryY) - sizeof(*_gridPowerHistoryY); + memmove(&_gridPowerHistoryY[0], &_gridPowerHistoryY[1], shift); + memmove(&_routedPowerHistoryY[0], &_routedPowerHistoryY[1], shift); + memmove(&_routerTHDiHistoryY[0], &_routerTHDiHistoryY[1], shift); // set new value _gridPowerHistoryY[YASOLR_GRAPH_POINTS - 1] = round(gridMetrics.power); @@ -673,6 +785,46 @@ void YaSolR::WebsiteClass::updateCharts() { _routerTHDiHistory.updateY(_routerTHDiHistoryY, YASOLR_GRAPH_POINTS); } +void YaSolR::WebsiteClass::updatePID() { + // shift array + constexpr size_t shift = sizeof(_pidInputHistoryY) - sizeof(*_pidInputHistoryY); + memmove(&_pidInputHistoryY[0], &_pidInputHistoryY[1], shift); + memmove(&_pidOutputHistoryY[0], &_pidOutputHistoryY[1], shift); + memmove(&_pidErrorHistoryY[0], &_pidErrorHistoryY[1], shift); + memmove(&_pidSumHistoryY[0], &_pidSumHistoryY[1], shift); + memmove(&_pidPTermHistoryY[0], &_pidPTermHistoryY[1], shift); + memmove(&_pidITermHistoryY[0], &_pidITermHistoryY[1], shift); + memmove(&_pidDTermHistoryY[0], &_pidDTermHistoryY[1], shift); + + // set new values + _pidInputHistoryY[YASOLR_GRAPH_POINTS - 1] = round(pidController.getInput()); + _pidOutputHistoryY[YASOLR_GRAPH_POINTS - 1] = round(pidController.getOutput()); + _pidErrorHistoryY[YASOLR_GRAPH_POINTS - 1] = round(pidController.getError()); + _pidSumHistoryY[YASOLR_GRAPH_POINTS - 1] = round(pidController.getSum()); + _pidPTermHistoryY[YASOLR_GRAPH_POINTS - 1] = round(pidController.getPTerm()); + _pidITermHistoryY[YASOLR_GRAPH_POINTS - 1] = round(pidController.getITerm()); + _pidDTermHistoryY[YASOLR_GRAPH_POINTS - 1] = round(pidController.getDTerm()); + + // update charts + _pidInputHistory.updateY(_pidInputHistoryY, YASOLR_GRAPH_POINTS); + _pidOutputHistory.updateY(_pidOutputHistoryY, YASOLR_GRAPH_POINTS); + _pidErrorHistory.updateY(_pidErrorHistoryY, YASOLR_GRAPH_POINTS); + _pidSumHistory.updateY(_pidSumHistoryY, YASOLR_GRAPH_POINTS); + _pidPTermHistory.updateY(_pidPTermHistoryY, YASOLR_GRAPH_POINTS); + _pidITermHistory.updateY(_pidITermHistoryY, YASOLR_GRAPH_POINTS); + _pidDTermHistory.updateY(_pidDTermHistoryY, YASOLR_GRAPH_POINTS); +} + +void YaSolR::WebsiteClass::resetPID() { + memset(_pidOutputHistoryY, 0, sizeof(_pidOutputHistoryY)); + memset(_pidInputHistoryY, 0, sizeof(_pidInputHistoryY)); + memset(_pidErrorHistoryY, 0, sizeof(_pidErrorHistoryY)); + memset(_pidSumHistoryY, 0, sizeof(_pidSumHistoryY)); + memset(_pidPTermHistoryY, 0, sizeof(_pidPTermHistoryY)); + memset(_pidITermHistoryY, 0, sizeof(_pidITermHistoryY)); + memset(_pidDTermHistoryY, 0, sizeof(_pidDTermHistoryY)); +} + void YaSolR::WebsiteClass::_sliderConfig(Card& card, const char* key) { card.attachCallback([key, &card](int value) { config.set(key, String(value)); @@ -681,6 +833,20 @@ void YaSolR::WebsiteClass::_sliderConfig(Card& card, const char* key) { }); } +void YaSolR::WebsiteClass::_floatConfig(Card& card, const char* key) { +#ifdef APP_MODEL_PRO + card.attachCallback([key, &card](const char* value) { + if (strlen(value) == 0) { + config.unset(key); + } else { + config.set(key, value); + } + card.update(config.get(key)); + dashboard.refreshCard(&card); + }); +#endif +} + void YaSolR::WebsiteClass::_numConfig(Card& card, const char* key) { #ifdef APP_MODEL_PRO card.attachCallback([key, &card](const char* value) { diff --git a/src/init/Boot.cpp b/src/init/Boot.cpp index fa734bb..da18e2e 100644 --- a/src/init/Boot.cpp +++ b/src/init/Boot.cpp @@ -53,6 +53,7 @@ Mycila::Task bootTask("Boot", [](void* params) { config.configure(KEY_ENABLE_OUTPUT2_DS18, YASOLR_FALSE); config.configure(KEY_ENABLE_OUTPUT2_PZEM, YASOLR_FALSE); config.configure(KEY_ENABLE_OUTPUT2_RELAY, YASOLR_FALSE); + config.configure(KEY_ENABLE_PID_VIEW, YASOLR_FALSE); config.configure(KEY_ENABLE_RELAY1, YASOLR_FALSE); config.configure(KEY_ENABLE_RELAY2, YASOLR_FALSE); config.configure(KEY_ENABLE_ZCD, YASOLR_FALSE); @@ -94,8 +95,8 @@ Mycila::Task bootTask("Boot", [](void* params) { config.configure(KEY_PID_IC_MODE, "2"); config.configure(KEY_PID_SETPOINT, "0"); config.configure(KEY_PID_KP, "0.3"); - config.configure(KEY_PID_KI, "0.4"); - config.configure(KEY_PID_KD, "0.2"); + config.configure(KEY_PID_KI, "0.3"); + config.configure(KEY_PID_KD, "0.1"); config.configure(KEY_PID_OUT_MIN, "-10000"); config.configure(KEY_PID_OUT_MAX, "10000"); config.configure(KEY_PIN_DISPLAY_SCL, String(YASOLR_DISPLAY_CLOCK_PIN)); diff --git a/src/init/Debug.cpp b/src/init/Debug.cpp index dff39f6..1892c22 100644 --- a/src/init/Debug.cpp +++ b/src/init/Debug.cpp @@ -5,7 +5,7 @@ #include static const Mycila::TaskDoneCallback LOG_EXEC_TIME = [](const Mycila::Task& me, const uint32_t elapsed) { - logger.debug(TAG, "%s in %" PRIu32 " us", me.getName(), elapsed); + logger.debug(TAG, "Task %s finished in %" PRIu32 " us", me.getName(), elapsed); }; Mycila::Task initLoggingTask("Init Logging", [](void* params) { diff --git a/src/init/Events.cpp b/src/init/Events.cpp index 77ed9a4..8849bff 100644 --- a/src/init/Events.cpp +++ b/src/init/Events.cpp @@ -206,6 +206,7 @@ Mycila::Task initEventsTask("Init Events", [](void* params) { pidController.setSetPoint(config.get(KEY_PID_SETPOINT).toFloat()); pidController.setTunings(config.get(KEY_PID_KP).toFloat(), config.get(KEY_PID_KI).toFloat(), config.get(KEY_PID_KD).toFloat()); pidController.setOutputLimits(config.get(KEY_PID_OUT_MIN).toFloat(), config.get(KEY_PID_OUT_MAX).toFloat()); + logger.info(TAG, "PID Controller reconfigured! Resetting..."); pidController.reset(); } diff --git a/src/init/HTTP.cpp b/src/init/HTTP.cpp index 1a85e30..893f70f 100644 --- a/src/init/HTTP.cpp +++ b/src/init/HTTP.cpp @@ -66,7 +66,7 @@ Mycila::Task initWebTask("Init Web", [](void* params) { if (type == WS_EVT_CONNECT) { logger.info(TAG, "Websocket client connected to: /ws/debug/pid"); wsDebugPID.cleanupClients(4); - client->text("pMode,dMode,icMode,rev,setpoint,kp,ki,kd,outMin,outMax,input,output,error,pTerm,iTerm,dTerm,sum"); + client->text("pMode,dMode,icMode,rev,setpoint,kp,ki,kd,outMin,outMax,input,output,error,sum,pTerm,iTerm,dTerm"); } }); webServer.addHandler(&wsDebugPID); diff --git a/src/main.cpp b/src/main.cpp index c2dfaed..2bf2502 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -65,7 +65,7 @@ void setup() { assert( ioTaskManager.asyncStart(512 * 9, 1, 1, 100, false)); // NOLINT assert( jsyTaskManager.asyncStart(512 * 4, 5, 0, 100, true)); // NOLINT assert( pzemTaskManager.asyncStart(512 * 4, 5, 0, 100, true)); // NOLINT - assert(routerTaskManager.asyncStart(512 * 3, 5, 1, 100, true)); // NOLINT + assert(routerTaskManager.asyncStart(512 * 6, 5, 1, 100, true)); // NOLINT // STARTUP READY! logger.info(TAG, "Started %s", Mycila::AppInfo.nameModelVersion.c_str()); diff --git a/src/tasks/Dashboard.cpp b/src/tasks/Dashboard.cpp index 6db60c9..b70b202 100644 --- a/src/tasks/Dashboard.cpp +++ b/src/tasks/Dashboard.cpp @@ -8,5 +8,7 @@ Mycila::Task dashboardTask("Dashboard", [](void* params) { YaSolR::Website.updateCards(); YaSolR::Website.updateCharts(); - dashboard.sendUpdates(); + if (!routingTask.isEnabled() || !config.getBool(KEY_ENABLE_PID_VIEW)) { + dashboard.sendUpdates(); + } }); diff --git a/src/tasks/Router.cpp b/src/tasks/Router.cpp index 088ba33..0366838 100644 --- a/src/tasks/Router.cpp +++ b/src/tasks/Router.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2023-2024 Mathieu Carbou and others */ #include +#include Mycila::Task routerTask("Router", [](void* params) { grid.applyExpiration(); @@ -37,42 +38,14 @@ Mycila::Task routingTask("Routing", Mycila::TaskType::ONCE, [](void* params) { Mycila::GridMetrics gridMetrics; grid.getMeasurements(gridMetrics); router.divert(gridMetrics.voltage, gridMetrics.power); - if (config.getBool(KEY_ENABLE_DEBUG)) { - String message; - message.reserve(256); - message.concat(pidController.getProportionalMode()); - message.concat(","); - message.concat(pidController.getDerivativeMode()); - message.concat(","); - message.concat(pidController.getIntegralCorrectionMode()); - message.concat(","); - message.concat(pidController.isReversed()); - message.concat(","); - message.concat(pidController.getSetPoint()); - message.concat(","); - message.concat(pidController.getKp()); - message.concat(","); - message.concat(pidController.getKi()); - message.concat(","); - message.concat(pidController.getKd()); - message.concat(","); - message.concat(pidController.getOutputMin()); - message.concat(","); - message.concat(pidController.getOutputMax()); - message.concat(","); - message.concat(pidController.getInput()); - message.concat(","); - message.concat(pidController.getOutput()); - message.concat(","); - message.concat(pidController.getError()); - message.concat(","); - message.concat(pidController.getPTerm()); - message.concat(","); - message.concat(pidController.getITerm()); - message.concat(","); - message.concat(pidController.getDTerm()); - message.concat(","); - message.concat(pidController.getSum()); - wsDebugPID.textAll(message); + if (config.getBool(KEY_ENABLE_PID_VIEW)) { + // Dashboard + YaSolR::Website.updatePID(); + dashboard.sendUpdates(); + + // WebSocket + AsyncWebSocketMessageBuffer* buffer = wsDebugPID.makeBuffer(256); + snprintf((char*)buffer->get(), 256, "%d,%d,%d,%d,%d,%.3f,%.3f,%.3f,%d,%d,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f", pidController.getProportionalMode(), pidController.getDerivativeMode(), pidController.getIntegralCorrectionMode(), pidController.isReversed(), static_cast(pidController.getSetPoint()), pidController.getKp(), pidController.getKi(), pidController.getKd(), static_cast(pidController.getOutputMin()), static_cast(pidController.getOutputMax()), pidController.getInput(), pidController.getOutput(), pidController.getError(), pidController.getSum(), pidController.getPTerm(), pidController.getITerm(), pidController.getDTerm()); // NOLINT + wsDebugPID.textAll(buffer); } });