Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commands to allow setting of timeprop parameters #19310

Merged
merged 3 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tasmota/my_user_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
211 changes: 147 additions & 64 deletions tasmota/tasmota_xdrv_driver/xdrv_48_timeprop.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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)
Expand All @@ -106,14 +101,17 @@ 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)
#endif
#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};
Expand All @@ -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) {
Expand All @@ -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);
blacknell marked this conversation as resolved.
Show resolved Hide resolved
}
} 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<TIMEPROP_NUM_OUTPUTS; i++) {
Expand All @@ -167,55 +282,20 @@ void TimepropXdrvPower(void) {
Tprop.current_relay_states = XdrvMailbox.index;
}

/* struct XDRVMAILBOX { */
/* uint16_t valid; */
/* uint16_t index; */
/* uint16_t data_len; */
/* int16_t payload; */
/* char *topic; */
/* char *data; */
/* } XdrvMailbox; */

// To get here post with topic cmnd/timeprop_setpower_n where n is index into timeprops 0:7
bool TimepropCommand()
{
char command [CMDSZ];
bool serviced = true;
uint8_t ua_prefix_len = strlen(D_CMND_TIMEPROP); // to detect prefix of command
/*
AddLog(LOG_LEVEL_INFO, PSTR("Command called: "
"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 (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<TIMEPROP_NUM_OUTPUTS; i++) {
ResponseAppend_P(PSTR("\"Output%d\":{"),i);
ResponseAppend_P(PSTR("\"CycleTime\":%d,"),cycleTimes[i]);
ResponseAppend_P(PSTR("\"DeadTime\":%d,"),deadTimes[i]);
ResponseAppend_P(PSTR("\"OutputInvert\":%d,"),opInverts[i]);
ResponseAppend_P(PSTR("\"FallbackPower\":%.2f,"),fallbacks[i]);
ResponseAppend_P(PSTR("\"MaxUpdateInterval\":%d"),maxIntervals[i]);
ResponseAppend_P(i<TIMEPROP_NUM_OUTPUTS-1 ? PSTR("},") : PSTR("}"));
}
return serviced;
ResponseAppend_P(PSTR("}"));
#endif // TIMEPROP_REPORT_SETTINGS
}

/*********************************************************************************************\
Expand All @@ -235,11 +315,14 @@ bool Xdrv48(uint32_t function) {
TimepropEverySecond();
break;
case FUNC_COMMAND:
result = TimepropCommand();
result = DecodeCommand(kCommands, Command);
break;
case FUNC_SET_POWER:
TimepropXdrvPower();
break;
case FUNC_JSON_APPEND:
ShowValues();
break;
}
return result;
}
Expand Down