From d7788faed857fd20bb791021fa2d8790d2168e3c Mon Sep 17 00:00:00 2001 From: Paul Blacknell Date: Mon, 14 Aug 2023 20:31:51 +0100 Subject: [PATCH 1/3] add: commands to set timeprop settings --- .../tasmota_xdrv_driver/xdrv_48_timeprop.ino | 211 ++++++++++++------ 1 file changed, 147 insertions(+), 64 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino b/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino index 22f25623f728..3d536d2a6f37 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino @@ -64,7 +64,7 @@ #define TIMEPROP_CYCLETIMES 60 // cycle time seconds #define TIMEPROP_DEADTIMES 0 // actuator action time seconds #define TIMEPROP_OPINVERTS false // whether to invert the output - #define TIMEPROP_FALLBACK_POWERS 0 // falls back to this if too long betwen power updates + #define TIMEPROP_FALLBACK_POWERS 0.0 // falls back to this if too long betwen power updates #define TIMEPROP_MAX_UPDATE_INTERVALS 120 // max no secs that are allowed between power updates (0 to disable) #define TIMEPROP_RELAYS 1 // which relay to control 1:8 @@ -75,7 +75,7 @@ #define TIMEPROP_CYCLETIMES 60, 10 // cycle time seconds #define TIMEPROP_DEADTIMES 0, 0 // actuator action time seconds #define TIMEPROP_OPINVERTS false, false // whether to invert the output - #define TIMEPROP_FALLBACK_POWERS 0, 0 // falls back to this if too long betwen power updates + #define TIMEPROP_FALLBACK_POWERS 0.0, 0.0 // falls back to this if too long betwen power updates #define TIMEPROP_MAX_UPDATE_INTERVALS 120, 120 // max no secs that are allowed between power updates (0 to disable) #define TIMEPROP_RELAYS 1, 2 // which relay to control 1:8 @@ -85,13 +85,8 @@ -#define D_CMND_TIMEPROP "timeprop_" -#define D_CMND_TIMEPROP_SETPOWER "setpower_" // add index no on end (0:8) and data is power 0:1 - #include "Timeprop.h" -enum TimepropCommands { CMND_TIMEPROP_SETPOWER }; -const char kTimepropCommands[] PROGMEM = D_CMND_TIMEPROP_SETPOWER; #ifndef TIMEPROP_NUM_OUTPUTS #define TIMEPROP_NUM_OUTPUTS 1 // how many outputs to control (with separate alogorithm for each) @@ -106,7 +101,7 @@ const char kTimepropCommands[] PROGMEM = D_CMND_TIMEPROP_SETPOWER; #define TIMEPROP_OPINVERTS false // whether to invert the output #endif #ifndef TIMEPROP_FALLBACK_POWERS -#define TIMEPROP_FALLBACK_POWERS 0 // falls back to this if too long betwen power updates +#define TIMEPROP_FALLBACK_POWERS 0 // falls back to this if too long between power updates #endif #ifndef TIMEPROP_MAX_UPDATE_INTERVALS #define TIMEPROP_MAX_UPDATE_INTERVALS 120 // max no secs that are allowed between power updates (0 to disable) @@ -114,6 +109,9 @@ const char kTimepropCommands[] PROGMEM = D_CMND_TIMEPROP_SETPOWER; #ifndef TIMEPROP_RELAYS #define TIMEPROP_RELAYS 1 // which relay to control 1:8 #endif +#ifndef TIMEPROP_REPORT_SETTINGS +#define TIMEPROP_REPORT_SETTINGS false // include timeprop settings in json output +#endif static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; static int relayNos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; @@ -126,6 +124,39 @@ struct { long current_time_secs = 0; // a counter that counts seconds since initialisation } Tprop; +#define D_CMND_TIMEPROP_PREFIX "Timeprop" +#define D_CMND_TIMEPROP_SETPOWER "_SetPower_" // underscores left in for backwards compatibility +#define D_CMND_TIMEPROP_SETCYCLETIME "SetCycleTime" +#define D_CMND_TIMEPROP_DEADTIME "SetDeadTime" +#define D_CMND_TIMEPROP_OPINVERT "SetOutputInvert" +#define D_CMND_TIMEPROP_FALLBACK_POWER "SetFallbackPower" +#define D_CMND_TIMEPROP_MAX_UPDATE_INTERVAL "SetMaxUpdateInterval" + +int cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES}; +int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES}; +unsigned char opInverts[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_OPINVERTS}; +float fallbacks[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_FALLBACK_POWERS}; +int maxIntervals[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_MAX_UPDATE_INTERVALS}; + +enum TimepropCommands { CMND_TIMEPROP_SETPOWER }; + +const char kCommands[] PROGMEM = D_CMND_TIMEPROP_PREFIX "|" + D_CMND_TIMEPROP_SETPOWER "|" + D_CMND_TIMEPROP_SETCYCLETIME "|" + D_CMND_TIMEPROP_DEADTIME "|" + D_CMND_TIMEPROP_OPINVERT "|" + D_CMND_TIMEPROP_FALLBACK_POWER "|" + D_CMND_TIMEPROP_MAX_UPDATE_INTERVAL ; + +void (* const Command[])(void) PROGMEM = { + &CmndSetPower, + &CmndSetCycleTime, + &CmndSetDeadTime, + &CmndSetOutputInvert, + &CmndSetFallbackPower, + &CmndSetMaxUpdateInterval, +}; + /* call this from elsewhere if required to set the power value for one of the timeprop instances */ /* index specifies which one, 0 up */ void TimepropSetPower(int index, float power) { @@ -135,19 +166,103 @@ void TimepropSetPower(int index, float power) { } void TimepropInit(void) { - // AddLog(LOG_LEVEL_INFO, PSTR("TPR: Timeprop Init")); - int cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES}; - int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES}; - int opInverts[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_OPINVERTS}; - int fallbacks[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_FALLBACK_POWERS}; - int maxIntervals[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_MAX_UPDATE_INTERVALS}; - for (int i = 0; i < TIMEPROP_NUM_OUTPUTS; i++) { Tprop.timeprops[i].initialise(cycleTimes[i], deadTimes[i], opInverts[i], fallbacks[i], maxIntervals[i], Tprop.current_time_secs); } } +void CmndSetPower(void) { + float newPower=CharToFloat(XdrvMailbox.data); + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS && newPower>=0.0 && newPower<=1.0) { + timeprops[XdrvMailbox.index].setPower(newPower, Tprop.current_time_secs ); + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_SETPOWER "%d\":\"%.2f\"}"), XdrvMailbox.index, newPower); + } +} + +// commands for settings all take the same form +// prefix commands with 'timeprop' +// then append the command string eg. 'SetCycleTime' +// then append the output number (0 for a single output) +// then append the value to set set, or +// leave blank to retrieve the current value +// eg. +// 'TimepropSetCycleTime0 120' will set the value of cycle time for output 0 to 120 +// 'TimepropSetCycleTime0' will retrieve the value of cycle time for output 0 + +void CmndSetCycleTime(void) { + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS ) { + if(XdrvMailbox.data_len) { + int newCycleTime=TextToInt(XdrvMailbox.data); + if(newCycleTime>0) { + cycleTimes[XdrvMailbox.index] = newCycleTime; + Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_SETCYCLETIME "%d\":\"%d\"}"), XdrvMailbox.index, newCycleTime); + } + } else { + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_SETCYCLETIME "%d\":\"%d\"}"), XdrvMailbox.index, cycleTimes[XdrvMailbox.index]); + } + } +} + +void CmndSetDeadTime(void) { + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS ) { + if(XdrvMailbox.data_len) { + int newDeadTime=TextToInt(XdrvMailbox.data); + if(newDeadTime>0) { + deadTimes[XdrvMailbox.index] = newDeadTime; + Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_DEADTIME "%d\":\"%d\"}"), XdrvMailbox.index, newDeadTime); + } + } else { + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_DEADTIME "%d\":\"%d\"}"), XdrvMailbox.index, deadTimes[XdrvMailbox.index]); + } + } +} + +void CmndSetOutputInvert(void) { + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS ) { + if(XdrvMailbox.data_len) { + unsigned char newInvert=TextToInt(XdrvMailbox.data); + opInverts[XdrvMailbox.index] = newInvert; + Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_OPINVERT "%d\":\"%d\"}"), XdrvMailbox.index, newInvert); + } else { + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_OPINVERT "%d\":\"%d\"}"), XdrvMailbox.index, opInverts[XdrvMailbox.index]); + } + } +} + +void CmndSetFallbackPower(void) { + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS ) { + if(XdrvMailbox.data_len) { + float newPower=CharToFloat(XdrvMailbox.data); + if(newPower>=0.0 && newPower<=1.0) { + fallbacks[XdrvMailbox.index] = newPower; + Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_FALLBACK_POWER "%d\":\"%.2f\"}"), XdrvMailbox.index, newPower); + } + } else { + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_FALLBACK_POWER "%d\":\"%.2f\"}"), XdrvMailbox.index, fallbacks[XdrvMailbox.index]); + } + } +} + +void CmndSetMaxUpdateInterval(void) { + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS ) { + if(XdrvMailbox.data_len) { + int newInterval=TextToInt(XdrvMailbox.data); + if(newInterval>0) { + maxIntervals[XdrvMailbox.index] = newInterval; + Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_MAX_UPDATE_INTERVAL "%d\":\"%d\"}"), XdrvMailbox.index, newInterval); + } + } else { + Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_MAX_UPDATE_INTERVAL "%d\":\"%d\"}"), XdrvMailbox.index, maxIntervals[XdrvMailbox.index]); + } + } +} + void TimepropEverySecond(void) { Tprop.current_time_secs++; // increment time for (int i=0; i= 0 ? XdrvMailbox.topic : ""), - (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); - */ - if (0 == strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_TIMEPROP), ua_prefix_len)) { - // command starts with timeprop_ - int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + ua_prefix_len, kTimepropCommands); - if (CMND_TIMEPROP_SETPOWER == command_code) { - /* - AddLog(LOG_LEVEL_INFO, PSTR("Timeprop command timeprop_setpower: " - "index: %d data_len: %d payload: %d topic: %s data: %s"), - XdrvMailbox.index, - XdrvMailbox.data_len, - XdrvMailbox.payload, - (XdrvMailbox.payload >= 0 ? XdrvMailbox.topic : ""), - (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); - */ - if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS) { - timeprops[XdrvMailbox.index].setPower( CharToFloat(XdrvMailbox.data), Tprop.current_time_secs ); - } - Response_P(PSTR("{\"" D_CMND_TIMEPROP D_CMND_TIMEPROP_SETPOWER "%d\":\"%s\"}"), XdrvMailbox.index, XdrvMailbox.data); - } - else { - serviced = false; - } - } else { - serviced = false; +void ShowValues(void) { +#if TIMEPROP_REPORT_SETTINGS + ResponseAppend_P(PSTR(",\"Timeprop\":{")); + for (int i=0; i Date: Mon, 14 Aug 2023 20:41:03 +0100 Subject: [PATCH 2/3] Update my_user_config.h --- tasmota/my_user_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 31e3e59350f3..e53b0c1728b9 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -1062,7 +1062,7 @@ #define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful // -- PID and Timeprop ------------------------------ // Both together will add +12k1 code -// #define use TIMEPROP // Add support for the timeprop feature (+9k1 code) +// #define USE_TIMEPROP // Add support for the timeprop feature (+9k1 code) // For details on the configuration please see the header of tasmota/xdrv_48_timeprop.ino // #define USE_PID // Add suport for the PID feature (+11k2 code) // For details on the configuration please see the header of tasmota/xdrv_49_pid.ino From cb88e909655cffa45806858a578d2f02e33ba0f1 Mon Sep 17 00:00:00 2001 From: Paul Blacknell Date: Mon, 14 Aug 2023 22:37:01 +0100 Subject: [PATCH 3/3] fix: properly generated json response to commands --- .../tasmota_xdrv_driver/xdrv_48_timeprop.ino | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino b/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino index 3d536d2a6f37..d1fc6754a05b 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino @@ -110,7 +110,7 @@ #define TIMEPROP_RELAYS 1 // which relay to control 1:8 #endif #ifndef TIMEPROP_REPORT_SETTINGS -#define TIMEPROP_REPORT_SETTINGS false // include timeprop settings in json output +#define TIMEPROP_REPORT_SETTINGS false // set to true to include timeprop settings in json output #endif static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; @@ -173,10 +173,15 @@ void TimepropInit(void) { } void CmndSetPower(void) { - float newPower=CharToFloat(XdrvMailbox.data); - if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS && newPower>=0.0 && newPower<=1.0) { - timeprops[XdrvMailbox.index].setPower(newPower, Tprop.current_time_secs ); - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_SETPOWER "%d\":\"%.2f\"}"), XdrvMailbox.index, newPower); + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS) { + if(XdrvMailbox.data_len) { + float newPower=CharToFloat(XdrvMailbox.data); + timeprops[XdrvMailbox.index].setPower(newPower, Tprop.current_time_secs ); + ResponseCmndFloat(newPower, 2); + } + else { + ResponseCmndError(); + } } } @@ -197,10 +202,14 @@ void CmndSetCycleTime(void) { if(newCycleTime>0) { cycleTimes[XdrvMailbox.index] = newCycleTime; Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_SETCYCLETIME "%d\":\"%d\"}"), XdrvMailbox.index, newCycleTime); + ResponseCmndNumber(newCycleTime); + } + else { + ResponseCmndError(); } - } else { - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_SETCYCLETIME "%d\":\"%d\"}"), XdrvMailbox.index, cycleTimes[XdrvMailbox.index]); + } + else { + ResponseCmndNumber(cycleTimes[XdrvMailbox.index]); } } } @@ -212,10 +221,14 @@ void CmndSetDeadTime(void) { if(newDeadTime>0) { deadTimes[XdrvMailbox.index] = newDeadTime; Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_DEADTIME "%d\":\"%d\"}"), XdrvMailbox.index, newDeadTime); + ResponseCmndNumber(newDeadTime); } - } else { - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_DEADTIME "%d\":\"%d\"}"), XdrvMailbox.index, deadTimes[XdrvMailbox.index]); + else { + ResponseCmndError(); + } + } + else { + ResponseCmndNumber(deadTimes[XdrvMailbox.index]); } } } @@ -226,9 +239,10 @@ void CmndSetOutputInvert(void) { unsigned char newInvert=TextToInt(XdrvMailbox.data); opInverts[XdrvMailbox.index] = newInvert; Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_OPINVERT "%d\":\"%d\"}"), XdrvMailbox.index, newInvert); - } else { - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_OPINVERT "%d\":\"%d\"}"), XdrvMailbox.index, opInverts[XdrvMailbox.index]); + ResponseCmndNumber(newInvert); + } + else { + ResponseCmndNumber(opInverts[XdrvMailbox.index]); } } } @@ -240,10 +254,14 @@ void CmndSetFallbackPower(void) { if(newPower>=0.0 && newPower<=1.0) { fallbacks[XdrvMailbox.index] = newPower; Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_FALLBACK_POWER "%d\":\"%.2f\"}"), XdrvMailbox.index, newPower); + ResponseCmndFloat(newPower, 2); + } + else { + ResponseCmndError(); } - } else { - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_FALLBACK_POWER "%d\":\"%.2f\"}"), XdrvMailbox.index, fallbacks[XdrvMailbox.index]); + } + else { + ResponseCmndFloat(fallbacks[XdrvMailbox.index], 2); } } } @@ -255,10 +273,14 @@ void CmndSetMaxUpdateInterval(void) { if(newInterval>0) { maxIntervals[XdrvMailbox.index] = newInterval; Tprop.timeprops[XdrvMailbox.index].initialise(cycleTimes[XdrvMailbox.index], deadTimes[XdrvMailbox.index], opInverts[XdrvMailbox.index], fallbacks[XdrvMailbox.index], maxIntervals[XdrvMailbox.index], Tprop.current_time_secs); - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_MAX_UPDATE_INTERVAL "%d\":\"%d\"}"), XdrvMailbox.index, newInterval); + ResponseCmndNumber(newInterval); + } + else { + ResponseCmndError(); } - } else { - Response_P(PSTR("{\"" D_CMND_TIMEPROP_PREFIX D_CMND_TIMEPROP_MAX_UPDATE_INTERVAL "%d\":\"%d\"}"), XdrvMailbox.index, maxIntervals[XdrvMailbox.index]); + } + else { + ResponseCmndNumber(maxIntervals[XdrvMailbox.index]); } } }