From 8eafc7d474b632b1c137f72f2ba2752c20329533 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 4 Jan 2021 10:29:12 +0100 Subject: [PATCH 01/28] as taken from https://github.com/colinl/Sonoff-Tasmota/tree/pid_branch --- lib/default/ProcessControl/PID.cpp | 193 ++++++++++++ lib/default/ProcessControl/PID.h | 90 ++++++ lib/default/ProcessControl/Timeprop.cpp | 94 ++++++ lib/default/ProcessControl/Timeprop.h | 85 ++++++ tasmota/xdrv_91_timeprop.ino | 225 ++++++++++++++ tasmota/xdrv_92_pid.ino | 385 ++++++++++++++++++++++++ 6 files changed, 1072 insertions(+) create mode 100644 lib/default/ProcessControl/PID.cpp create mode 100644 lib/default/ProcessControl/PID.h create mode 100644 lib/default/ProcessControl/Timeprop.cpp create mode 100644 lib/default/ProcessControl/Timeprop.h create mode 100644 tasmota/xdrv_91_timeprop.ino create mode 100644 tasmota/xdrv_92_pid.ino diff --git a/lib/default/ProcessControl/PID.cpp b/lib/default/ProcessControl/PID.cpp new file mode 100644 index 000000000..0a8e35356 --- /dev/null +++ b/lib/default/ProcessControl/PID.cpp @@ -0,0 +1,193 @@ +/** + * Copyright 2018 Colin Law + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * See Timeprop.h for Usage + * + **/ + + +#include "PID.h" + +PID::PID() { + m_initialised = 0; + m_last_sample_time = 0; + m_last_pv_update_time = 0; + m_last_power = 0.0; +} + +void PID::initialise( double setpoint, double prop_band, double t_integral, double t_derivative, + double integral_default, int max_interval, double smooth_factor, unsigned char mode_auto, double manual_op ) { + + m_setpoint = setpoint; + m_prop_band = prop_band; + m_t_integral = t_integral; + m_t_derivative = t_derivative; + m_integral_default = integral_default; + m_max_interval = max_interval; + m_smooth_factor= smooth_factor; + m_mode_auto= mode_auto; + m_manual_op = manual_op; + + m_initialised = 1; + +} + + +/* called regularly to calculate and return new power value */ +double PID::tick( unsigned long nowSecs ) { + double power; + double factor; + if (m_initialised && m_last_pv_update_time) { + // we have been initialised and have been given a pv value + // check whether too long has elapsed since pv was last updated + if (m_max_interval > 0 && nowSecs - m_last_pv_update_time > m_max_interval) { + // yes, too long has elapsed since last PV update so go to fallback power + power = m_manual_op; + } else { + // is this the first time through here? + if (m_last_sample_time) { + // not first time + unsigned long delta_t = nowSecs - m_last_sample_time; // seconds + if (delta_t <= 0 || delta_t > m_max_interval) { + // too long since last sample so leave integral as is and set deriv to zero + m_derivative = 0; + } else { + if (m_smooth_factor > 0) { + // A derivative smoothing factor has been supplied + // smoothing time constant is td/factor but with a min of delta_t to stop overflows + int ts = m_t_derivative/m_smooth_factor > delta_t ? m_t_derivative/m_smooth_factor : delta_t; + factor = 1.0/(ts/delta_t); + } else { + // no integral smoothing so factor is 1, this makes smoothed_value the previous pv + factor = 1.0; + } + double delta_v = (m_pv - m_smoothed_value) * factor; + m_smoothed_value = m_smoothed_value + delta_v; + m_derivative = m_t_derivative * delta_v/delta_t; + // lock the integral if abs(previous integral + error) > prop_band/2 + // as this means that P + I is outside the linear region so power will be 0 or full + // also lock if control is disabled + double error = m_pv - m_setpoint; + double pbo2 = m_prop_band/2.0; + double epi = error + m_integral; + if (epi < 0.0) epi = -epi; // abs value of error + m_integral + if (epi < pbo2 && m_mode_auto) { + if (m_t_integral <= 0) { + // t_integral is zero (or silly), set integral to one end or the other + // or half way if exactly on sp + if (error > 0.0) { + m_integral = pbo2; + } else if (error < 0) { + m_integral = -pbo2; + } else { + m_integral = 0.0; + } + } else { + m_integral = m_integral + error * delta_t/m_t_integral; + } + } + // clamp to +- 0.5 prop band widths so that it cannot push the zero power point outside the pb + // do this here rather than when integral is updated to allow for the fact that the pb may change dynamically + if ( m_integral < -pbo2 ) { + m_integral = -pbo2; + } else if (m_integral > pbo2) { + m_integral = pbo2; + } + } + + } else { + // first time through, initialise context data + m_smoothed_value = m_pv; + // setup the integral term so that the power out would be integral_default if pv=setpoint + m_integral = (0.5 - m_integral_default)*m_prop_band; + m_derivative = 0.0; + } + + double proportional = m_pv - m_setpoint; + if (m_prop_band == 0) { + // prop band is zero so drop back to on/off control with zero hysteresis + if (proportional > 0.0) { + power = 0.0; + } else if (proportional < 0.0) { + power = 1.0; + } else { + // exactly on sp so leave power as it was last time round + power = m_last_power; + } + } + else { + power = -1.0/m_prop_band * (proportional + m_integral + m_derivative) + 0.5; + } + // set power to disabled value if the loop is not enabled + if (!m_mode_auto) { + power = m_manual_op; + } + m_last_sample_time = nowSecs; + } + } else { + // not yet initialised or no pv value yet so set power to disabled value + power = m_manual_op; + } + if (power < 0.0) { + power = 0.0; + } else if (power > 1.0) { + power = 1.0; + } + m_last_power = power; + return power; +} + +// call to pass in new process value +void PID::setPv( double pv, unsigned long nowSecs ){ + m_pv = pv; + m_last_pv_update_time = nowSecs; +} + +// methods to modify configuration data +void PID::setSp( double setpoint ) { + m_setpoint = setpoint; +} + +void PID::setPb( double prop_band ) { + m_prop_band = prop_band; +} + +void PID::setTi( double t_integral ) { + m_t_integral = t_integral; +} + +void PID::setTd( double t_derivative ) { + m_t_derivative = t_derivative; +} + +void PID::setInitialInt( double integral_default ) { + m_integral_default = integral_default; +} + +void PID::setDSmooth( double smooth_factor ) { + m_smooth_factor = smooth_factor; +} + +void PID::setAuto( unsigned char mode_auto ) { + m_mode_auto = mode_auto; +} + +void PID::setManualPower( double manual_op ) { + m_manual_op = manual_op; +} + +void PID::setMaxInterval( int max_interval ) { + m_max_interval = max_interval; +} diff --git a/lib/default/ProcessControl/PID.h b/lib/default/ProcessControl/PID.h new file mode 100644 index 000000000..3409da4a2 --- /dev/null +++ b/lib/default/ProcessControl/PID.h @@ -0,0 +1,90 @@ +/** + * Copyright 2018 Colin Law + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + /** + * A PID control class + * + * Github repository https://github.com/colinl/process-control.git + * + * Given ... + * + * Usage: + * First call initialise(), see below for parameters then + * ... + * The functions require a parameter nowSecs which is a representation of the + * current time in seconds. The absolute value of this is immaterial, it is + * used for relative timing only. + * + **/ + + +#ifndef PID_h +#define PID_h + +class PID { +public: + + PID(); + + /* + Initialiser given + + current time in seconds + */ + void initialise( double setpoint, double prop_band, double t_integral, double t_derivative, + double integral_default, int max_interval, double smooth_factor, unsigned char mode_auto, double manual_op ); + + + /* called regularly to calculate and return new power value */ + double tick(unsigned long nowSecs); + + // call to pass in new process value + void setPv( double pv, unsigned long nowSecs ); + + // methods to modify configuration data + void setSp( double setpoint ); + void setPb( double prop_band ); + void setTi( double t_integral ); + void setTd( double t_derivative ); + void setInitialInt( double integral_default ); + void setDSmooth( double smooth_factor ); + void setAuto( unsigned char mode_auto ); + void setManualPower( double manual_op ); + void setMaxInterval( int max_interval ); + +private: + double m_pv; + double m_setpoint; + double m_prop_band; + double m_t_integral; + double m_t_derivative; + double m_integral_default; + double m_smooth_factor; + unsigned char m_mode_auto; + double m_manual_op; + int m_max_interval; + double m_last_power; + + + unsigned char m_initialised; + unsigned long m_last_pv_update_time; // the time of last pv update secs + unsigned long m_last_sample_time; // the time of the last tick() run + double m_smoothed_value; + double m_integral; + double m_derivative ; +}; + +#endif // Timeprop_h diff --git a/lib/default/ProcessControl/Timeprop.cpp b/lib/default/ProcessControl/Timeprop.cpp new file mode 100644 index 000000000..c4d5e9eb8 --- /dev/null +++ b/lib/default/ProcessControl/Timeprop.cpp @@ -0,0 +1,94 @@ +/** + * Copyright 2018 Colin Law + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * See Timeprop.h for Usage + * + **/ + + +#include "Timeprop.h" + +void Timeprop::initialise( int cycleTime, int deadTime, unsigned char invert, float fallbackPower, int maxUpdateInterval, + unsigned long nowSecs) { + m_cycleTime = cycleTime; + m_deadTime = deadTime; + m_invert = invert; + m_fallbackPower = fallbackPower; + m_maxUpdateInterval = maxUpdateInterval; + + m_dtoc = (float)deadTime/cycleTime; + m_opState = 0; + setPower(m_fallbackPower, nowSecs); +} + +/* set current power required 0:1, given power and current time in seconds */ +void Timeprop::setPower( float power, unsigned long nowSecs ) { + if (power < 0.0) { + power = 0.0; + } else if (power >= 1.0) { + power = 1.0; + } + m_power = power; + m_lastPowerUpdateTime = nowSecs; +}; + +/* called regularly to provide new output value */ +/* returns new o/p state 0, 1 */ +int Timeprop::tick( unsigned long nowSecs) { + int newState; + float wave; + float direction; + float effectivePower; + + // check whether too long has elapsed since power was last updated + if (m_maxUpdateInterval > 0 && nowSecs - m_lastPowerUpdateTime > m_maxUpdateInterval) { + // yes, go to fallback power + setPower(m_fallbackPower, nowSecs); + } + + wave = (nowSecs % m_cycleTime)/(float)m_cycleTime; + // determine direction of travel and convert to triangular wave + if (wave < 0.5) { + direction = 1; // on the way up + wave = wave*2; + } else { + direction = -1; // on the way down + wave = (1 - wave)*2; + } + // if a dead_time has been supplied for this o/p then adjust power accordingly + if (m_deadTime > 0 && m_power > 0.0 && m_power < 1.0) { + effectivePower = (1.0-2.0*m_dtoc)*m_power + m_dtoc; + } else { + effectivePower = m_power; + } + // cope with end cases in case values outside 0..1 + if (effectivePower <= 0.0) { + newState = 0; // no heat + } else if (effectivePower >= 1.0) { + newState = 1; // full heat + } else { + // only allow power to come on on the way down and off on the way up, to reduce short pulses + if (effectivePower >= wave && direction == -1) { + newState = 1; + } else if (effectivePower <= wave && direction == 1) { + newState = 0; + } else { + // otherwise leave it as it is + newState = m_opState; + } + } + m_opState = newState; + return m_invert ? (1-m_opState) : m_opState; +} diff --git a/lib/default/ProcessControl/Timeprop.h b/lib/default/ProcessControl/Timeprop.h new file mode 100644 index 000000000..c6df45be0 --- /dev/null +++ b/lib/default/ProcessControl/Timeprop.h @@ -0,0 +1,85 @@ +/** + * Copyright 2018 Colin Law + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + /** + * A class to generate a time proportioned digital output from a linear input + * + * Github repository https://github.com/colinl/process-control.git + * + * Given a required power value in the range 0.0 to 1.0 this class generates + * a time proportioned 0/1 output (representing OFF/ON) which averages to the + * required power value. The cycle time is configurable. If, for example, this + * is set to 10 minutes and the power input is 0.2 then the output will be on + * for two minutes in every ten minutes. + * + * A value for actuator dead time may be provided. If you have a device that + * takes a significant time to open/close then set this to the average of the + * open and close times. The algorithim will then adjust the output timing + * accordingly to ensure that the output is not switched more rapidly than + * the actuator can cope with. + * + * A facility to invert the output is provided which can be useful when used in + * refrigeration processes and similar. + * + * Usage: + * First call initialise(), see below for parameters then call setPower() to + * specify the current power required. + * Then regularly call tick() to determine the output state required. + * setPower may be called as often as required to change the power required. + * The functions require a parameter nowSecs which is a representation of the + * current time in seconds. The absolute value of this is immaterial, it is + * used for relative timing only. + * + **/ + + +#ifndef Timeprop_h +#define Timeprop_h + +class Timeprop { +public: + /* + Initialiser given + cycleTime seconds + actuator deadTime seconds + whether to invert the output + fallback power value if updates are not received within time below + max number of seconds to allow between power updates before falling back to default power (0 to disable) + current time in seconds + */ + void initialise( int cycleTime, int deadTime, unsigned char invert, float fallbackPower, int maxUpdateInterval, + unsigned long nowSecs); + + /* set current power required 0:1, given power and current time in seconds */ + void setPower( float power, unsigned long nowSecs ); + + /* called regularly to provide new output value */ + /* returns new o/p state 0, 1 */ + int tick(unsigned long nowSecs); + +private: + int m_cycleTime; // cycle time seconds, float to force float calcs + int m_deadTime; // actuator action time seconds + unsigned char m_invert; // whether to invert the output + float m_dtoc; // deadTime/m_cycleTime + int m_opState; // current output state (before invert) + float m_power; // required power 0:1 + float m_fallbackPower; // falls back to this if updates not received with max allowed timezone + int m_maxUpdateInterval; // max time between updates + unsigned long m_lastPowerUpdateTime; // the time of last power update secs +}; + +#endif // Timeprop_h diff --git a/tasmota/xdrv_91_timeprop.ino b/tasmota/xdrv_91_timeprop.ino new file mode 100644 index 000000000..ba3f0af4b --- /dev/null +++ b/tasmota/xdrv_91_timeprop.ino @@ -0,0 +1,225 @@ +/* + xdrv_91_timeprop.ino - Timeprop support for Sonoff-Tasmota + Copyright (C) 2018 Colin Law and Thomas Herrmann + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * Code to drive one or more relays in a time proportioned manner give a + * required power value. + * + * Given required power values in the range 0.0 to 1.0 the relays will be + * driven on/off in such that the average power suppled will represent + * the required power. + * The cycle time is configurable. If, for example, the + * period is set to 10 minutes and the power input is 0.2 then the output will + * be on for two minutes in every ten minutes. + * + * A value for actuator dead time may be provided. If you have a device that + * takes a significant time to open/close then set this to the average of the + * open and close times. The algorithim will then adjust the output timing + * accordingly to ensure that the output is not switched more rapidly than + * the actuator can cope with. + * + * A facility to invert the output is provided which can be useful when used in + * refrigeration processes and similar. + * + * In the case where only one relay is being driven the power value is set by + * writing the value to the mqtt topic cmnd/timeprop_setpower_0. If more than + * one relay is being driven (as might be the case for a heat/cool application + * where one relay drives the heater and the other the cooler) then the power + * for the second relay is written to topic cmnd/timeprop_setpower_1 and so on. + * + * To cope with the problem of temporary wifi failure etc a + * TIMEPROP_MAX_UPDATE_INTERVALS value is available. This can be set to the max + * expected time between power updates and if this time is exceeded then the + * power will fallback to a given safe value until a new value is provided. Set + * the interval to 0 to disable this feature. + * + * Usage: + * Place this file in the sonoff folder. + * Clone the library https://github.com/colinl/process-control.git from Github + * into a subfolder of lib. + * In user_config.h or user_config_override.h for a single relay, include + * code as follows: + + #define USE_TIMEPROP // include the timeprop feature (+1.2k) + // for single output + #define TIMEPROP_NUM_OUTPUTS 1 // how many outputs to control (with separate alogorithm for each) + #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_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 + + * or for two relays: + #define USE_TIMEPROP // include the timeprop feature (+1.2k) + // for single output + #define TIMEPROP_NUM_OUTPUTS 2 // how many outputs to control (with separate alogorithm for each) + #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_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 + + * Publish values between 0 and 1 to the topic(s) described above + * +**/ + + +#ifdef USE_TIMEPROP + +# include "Timeprop.h" + +#define D_CMND_TIMEPROP "timeprop_" +#define D_CMND_TIMEPROP_SETPOWER "setpower_" // add index no on end (0:8) and data is power 0:1 + +enum TimepropCommands { CMND_TIMEPROP_SETPOWER }; +const char kTimepropCommands[] PROGMEM = D_CMND_TIMEPROP_SETPOWER; + +static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; +static int relayNos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; +static long currentRelayStates = 0; // current actual relay states. Bit 0 first relay + +static long timeprop_current_time_secs = 0; // a counter that counts seconds since initialisation + +/* call this from elsewhere if required to set the power value for one of the timeprop instances */ +/* index specifies which one, 0 up */ +void Timeprop_Set_Power( int index, float power ) +{ + if (index >= 0 && index < TIMEPROP_NUM_OUTPUTS) + { + timeprops[index].setPower( power, timeprop_current_time_secs); + } +} + +void Timeprop_Init() +{ + snprintf_P(log_data, sizeof(log_data), "Timeprop Init"); + AddLog(LOG_LEVEL_INFO); + 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= 0 ? XdrvMailbox.topic : ""), + (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); + + AddLog(LOG_LEVEL_INFO); + */ + 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) { + /* + snprintf_P(log_data, sizeof(log_data), "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 : "")); + AddLog(LOG_LEVEL_INFO); + */ + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS) { + timeprops[XdrvMailbox.index].setPower( atof(XdrvMailbox.data), timeprop_current_time_secs ); + } + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_TIMEPROP D_CMND_TIMEPROP_SETPOWER "%d\":\"%s\"}"), + XdrvMailbox.index, XdrvMailbox.data); + } + else { + serviced = false; + } + } else { + serviced = false; + } + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +#define XDRV_91 91 + +boolean Xdrv91(byte function) +//boolean XDRV_91(byte function) +{ + boolean result = false; + + switch (function) { + case FUNC_INIT: + Timeprop_Init(); + break; + case FUNC_EVERY_SECOND: + Timeprop_Every_Second(); + break; + case FUNC_COMMAND: + result = Timeprop_Command(); + break; + case FUNC_SET_POWER: + Timeprop_Xdrv_Power(); + break; + } + return result; +} + +#endif // USE_TIMEPROP diff --git a/tasmota/xdrv_92_pid.ino b/tasmota/xdrv_92_pid.ino new file mode 100644 index 000000000..e05f824e5 --- /dev/null +++ b/tasmota/xdrv_92_pid.ino @@ -0,0 +1,385 @@ +/* + xdrv_92_pid.ino - PID algorithm plugin for Sonoff-Tasmota + Copyright (C) 2018 Colin Law and Thomas Herrmann + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * Code to + * + * Usage: + * Place this file in the sonoff folder. + * Clone the library https://github.com/colinl/process-control.git from Github + * into a subfolder of lib. + * If you want to use a time proportioned relay output with this then also get + * xdrv_91_timeprop.ino + * In user_config.h or user_config_override.h include code as follows: + + #define USE_PID // include the pid feature (+4.3k) + #define PID_SETPOINT 19.5 // Setpoint value. This is the process value that the process is + // aiming for. + // May be adjusted via MQTT using cmnd pid_sp + + #define PID_PROPBAND 5 // Proportional band in process units (eg degrees). This controls + // the gain of the loop and is the range of process value over which + // the power output will go from 0 to full power. The units are that + // of the process and setpoint, so for example in a heating + // application it might be set to 1.5 degrees. + // May be adjusted via MQTT using cmnd pid_pb + + #define PID_INTEGRAL_TIME 1800 // Integral time seconds. This is a setting for the integral time, + // in seconds. It represents the time constant of the integration + // effect. The larger the value the slower the integral effect will be. + // Obviously the slower the process is the larger this should be. For + // example for a domestic room heated by convection radiators a setting + // of one hour might be appropriate (in seconds). To disable the + // integral effect set this to a large number. + // May be adjusted via MQTT using cmnd pid_ti + + #define PID_DERIVATIVE_TIME 15 // Derivative time seconds. This is a setting for the derivative time, + // in seconds. It represents the time constant of the derivative effect. + // The larger the value the greater will be the derivative effect. + // Typically this will be set to somewhat less than 25% of the integral + // setting, once the integral has been adjusted to the optimum value. To + // disable the derivative effect set this to 0. When initially tuning a + // loop it is often sensible to start with derivative zero and wind it in + // once other parameters have been setup. + // May be adjusted via MQTT using cmnd pid_td + + #define PID_INITIAL_INT 0.5 // Initial integral value (0:1). This is an initial value which is used + // to preset the integrated error value when the flow is deployed in + // order to assist in homing in on the setpoint the first time. It should + // be set to an estimate of what the power requirement might be in order + // to maintain the process at the setpoint. For example for a domestic + // room heating application it might be set to 0.2 indicating that 20% of + // the available power might be required to maintain the setpoint. The + // value is of no consequence apart from device restart. + + #define PID_MAX_INTERVAL 300 // This is the maximum time in seconds that is expected between samples. + // It is provided to cope with unusual situations such as a faulty sensor + // that might prevent the node from being supplied with a process value. + // If no new process value is received for this time then the power is set + // to the value defined for PID_MANUAL_POWER. + // May be adjusted via MQTT using cmnd pid_max_interval + + #define PID_DERIV_SMOOTH_FACTOR 3 // In situations where the process sensor has limited resolution (such as + // the DS18B20), the use of deriviative can be problematic as when the + // process is changing only slowly the steps in the value cause spikes in + // the derivative. To reduce the effect of these this parameter can be + // set to apply a filter to the derivative term. I have found that with + // the DS18B20 that a value of 3 here can be beneficial, providing + // effectively a low pass filter on the derivative at 1/3 of the derivative + // time. This feature may also be useful if the process value is particularly + // noisy. The smaller the value the greater the filtering effect but the + // more it will reduce the effectiveness of the derivative. A value of zero + // disables this feature. + // May be adjusted via MQTT using cmnd pid_d_smooth + + #define PID_AUTO 1 // Auto mode 1 or 0 (for manual). This can be used to enable or disable + // the control (1=enable, auto mode, 0=disabled, manual mode). When in + // manual mode the output is set the value definded for PID_MANUAL_POWER + // May be adjusted via MQTT using cmnd pid_auto + + #define PID_MANUAL_POWER 0 // Power output when in manual mode or fallback mode if too long elapses + // between process values + // May be adjusted via MQTT using cmnd pid_manual_power + + #define PID_UPDATE_SECS 0 // How often to run the pid algorithm (integer secs) or 0 to run the algorithm + // each time a new pv value is received, for most applictions specify 0. + // Otherwise set this to a time + // that is short compared to the response of the process. For example, + // something like 15 seconds may well be appropriate for a domestic room + // heating application. + // May be adjusted via MQTT using cmnd pid_update_secs + + #define PID_USE_TIMPROP 1 // To use an internal relay for a time proportioned output to drive the + // process, set this to indicate which timeprop output to use. For a device + // with just one relay then this will be 1. + // It is then also necessary to define USE_TIMEPROP and set the output up as + // explained in xdrv_91_timeprop.ino + // To disable this feature leave this undefined (undefined, not defined to nothing). + + #define PID_USE_LOCAL_SENSOR // if defined then the local sensor will be used for pv. Leave undefined if + // this is not required. The rate that the sensor is read is defined by TELE_PERIOD + // If not using the sensor then you can supply process values via MQTT using + // cmnd pid_pv + + //#define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the + // number of the shutter here. Otherwise leave this commented out + + * Help with using the PID algorithm and with loop tuning can be found at + * http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html + * This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on + * the code there and the tuning techique described there should work just the same. + + * +**/ + + +#ifdef USE_PID + +#include "PID.h" + +#define D_CMND_PID "pid_" + +#define D_CMND_PID_SETPV "pv" +#define D_CMND_PID_SETSETPOINT "sp" +#define D_CMND_PID_SETPROPBAND "pb" +#define D_CMND_PID_SETINTEGRAL_TIME "ti" +#define D_CMND_PID_SETDERIVATIVE_TIME "td" +#define D_CMND_PID_SETINITIAL_INT "initint" +#define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "d_smooth" +#define D_CMND_PID_SETAUTO "auto" +#define D_CMND_PID_SETMANUAL_POWER "manual_power" +#define D_CMND_PID_SETMAX_INTERVAL "max_interval" +#define D_CMND_PID_SETUPDATE_SECS "update_secs" + +enum PIDCommands { CMND_PID_SETPV, CMND_PID_SETSETPOINT, CMND_PID_SETPROPBAND, CMND_PID_SETINTEGRAL_TIME, + CMND_PID_SETDERIVATIVE_TIME, CMND_PID_SETINITIAL_INT, CMND_PID_SETDERIV_SMOOTH_FACTOR, CMND_PID_SETAUTO, + CMND_PID_SETMANUAL_POWER, CMND_PID_SETMAX_INTERVAL, CMND_PID_SETUPDATE_SECS }; +const char kPIDCommands[] PROGMEM = D_CMND_PID_SETPV "|" D_CMND_PID_SETSETPOINT "|" D_CMND_PID_SETPROPBAND "|" + D_CMND_PID_SETINTEGRAL_TIME "|" D_CMND_PID_SETDERIVATIVE_TIME "|" D_CMND_PID_SETINITIAL_INT "|" D_CMND_PID_SETDERIV_SMOOTH_FACTOR "|" + D_CMND_PID_SETAUTO "|" D_CMND_PID_SETMANUAL_POWER "|" D_CMND_PID_SETMAX_INTERVAL "|" D_CMND_PID_SETUPDATE_SECS; + +static PID pid; +static int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run +static int max_interval = PID_MAX_INTERVAL; +static unsigned long last_pv_update_secs = 0; +static boolean run_pid_now = false; // tells PID_Every_Second to run the pid algorithm + +static long pid_current_time_secs = 0; // a counter that counts seconds since initialisation + +void PID_Init() +{ + snprintf_P(log_data, sizeof(log_data), "PID Init"); + AddLog(LOG_LEVEL_INFO); + pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, + PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); +} + +void PID_Every_Second() { + static int sec_counter = 0; + pid_current_time_secs++; // increment time + // run the pid algorithm if run_pid_now is true or if the right number of seconds has passed or if too long has + // elapsed since last pv update. If too long has elapsed the the algorithm will deal with that. + if (run_pid_now || pid_current_time_secs - last_pv_update_secs > max_interval || (update_secs != 0 && sec_counter++ % update_secs == 0)) { + run_pid(); + run_pid_now = false; + } +} + +void PID_Show_Sensor() { + // Called each time new sensor data available, data in mqtt data in same format + // as published in tele/SENSOR + // Update period is specified in TELE_PERIOD + // e.g. "{"Time":"2018-03-13T16:48:05","DS18B20":{"Temperature":22.0},"TempUnit":"C"}" + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor: mqtt_data: %s", mqtt_data); + AddLog(LOG_LEVEL_INFO); + StaticJsonBuffer<400> jsonBuffer; + // force mqtt_data to read only to stop parse from overwriting it + JsonObject& data_json = jsonBuffer.parseObject((const char*)mqtt_data); + if (data_json.success()) { + const char* value = data_json["DS18B20"]["Temperature"]; + // check that something was found and it contains a number + if (value != NULL && strlen(value) > 0 && (isdigit(value[0]) || (value[0] == '-' && isdigit(value[1])) ) ) { + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor: Temperature: %s", value); + AddLog(LOG_LEVEL_INFO); + // pass the value to the pid alogorithm to use as current pv + last_pv_update_secs = pid_current_time_secs; + pid.setPv(atof(value), last_pv_update_secs); + // also trigger running the pid algorithm if we have been told to run it each pv sample + if (update_secs == 0) { + // this runs it at the next second + run_pid_now = true; + } + } else { + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor - no temperature found"); + AddLog(LOG_LEVEL_INFO); + } + } else { + // parse failed + snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor - json parse failed"); + AddLog(LOG_LEVEL_INFO); + } +} + + +/* struct XDRVMAILBOX { */ +/* uint16_t valid; */ +/* uint16_t index; */ +/* uint16_t data_len; */ +/* int16_t payload; */ +/* char *topic; */ +/* char *data; */ +/* } XdrvMailbox; */ + +boolean PID_Command() +{ + char command [CMDSZ]; + boolean serviced = true; + uint8_t ua_prefix_len = strlen(D_CMND_PID); // to detect prefix of command + + snprintf_P(log_data, sizeof(log_data), "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 : "")); + AddLog(LOG_LEVEL_INFO); + + if (0 == strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_PID), ua_prefix_len)) { + // command starts with pid_ + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + ua_prefix_len, kPIDCommands); + serviced = true; + switch (command_code) { + case CMND_PID_SETPV: + snprintf_P(log_data, sizeof(log_data), "PID command setpv"); + AddLog(LOG_LEVEL_INFO); + last_pv_update_secs = pid_current_time_secs; + pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); + // also trigger running the pid algorithm if we have been told to run it each pv sample + if (update_secs == 0) { + // this runs it at the next second + run_pid_now = true; + } + break; + + case CMND_PID_SETSETPOINT: + snprintf_P(log_data, sizeof(log_data), "PID command setsetpoint"); + AddLog(LOG_LEVEL_INFO); + pid.setSp(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETPROPBAND: + snprintf_P(log_data, sizeof(log_data), "PID command propband"); + AddLog(LOG_LEVEL_INFO); + pid.setPb(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETINTEGRAL_TIME: + snprintf_P(log_data, sizeof(log_data), "PID command Ti"); + AddLog(LOG_LEVEL_INFO); + pid.setTi(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETDERIVATIVE_TIME: + snprintf_P(log_data, sizeof(log_data), "PID command Td"); + AddLog(LOG_LEVEL_INFO); + pid.setTd(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETINITIAL_INT: + snprintf_P(log_data, sizeof(log_data), "PID command initial int"); + AddLog(LOG_LEVEL_INFO); + pid.setInitialInt(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETDERIV_SMOOTH_FACTOR: + snprintf_P(log_data, sizeof(log_data), "PID command deriv smooth"); + AddLog(LOG_LEVEL_INFO); + pid.setDSmooth(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETAUTO: + snprintf_P(log_data, sizeof(log_data), "PID command auto"); + AddLog(LOG_LEVEL_INFO); + pid.setAuto(atoi(XdrvMailbox.data)); + break; + + case CMND_PID_SETMANUAL_POWER: + snprintf_P(log_data, sizeof(log_data), "PID command manual power"); + AddLog(LOG_LEVEL_INFO); + pid.setManualPower(atof(XdrvMailbox.data)); + break; + + case CMND_PID_SETMAX_INTERVAL: + snprintf_P(log_data, sizeof(log_data), "PID command set max interval"); + AddLog(LOG_LEVEL_INFO); + max_interval = atoi(XdrvMailbox.data); + pid.setMaxInterval(max_interval); + break; + + case CMND_PID_SETUPDATE_SECS: + snprintf_P(log_data, sizeof(log_data), "PID command set update secs"); + AddLog(LOG_LEVEL_INFO); + update_secs = atoi(XdrvMailbox.data) ; + if (update_secs < 0) update_secs = 0; + break; + + default: + serviced = false; + } + + if (serviced) { + // set mqtt RESULT + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"), XdrvMailbox.topic, XdrvMailbox.data); + } + + } else { + serviced = false; + } + return serviced; +} + +static void run_pid() +{ + double power = pid.tick(pid_current_time_secs); + char buf[10]; + dtostrfd(power, 3, buf); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", buf); + MqttPublishPrefixTopic_P(TELE, "PID", false); + +#if defined PID_SHUTTER + // send output as a position from 0-100 to defined shutter + int pos = power * 100; + ShutterSetPosition(PID_SHUTTER, pos); +#endif //PID_SHUTTER + +#if defined PID_USE_TIMPROP + // send power to appropriate timeprop output + Timeprop_Set_Power( PID_USE_TIMPROP-1, power ); +#endif // PID_USE_TIMPROP +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +#define XDRV_92 92 + +boolean Xdrv92(byte function) +//boolean XDRV_92(byte function) +{ + boolean result = false; + + switch (function) { + case FUNC_INIT: + PID_Init(); + break; + case FUNC_EVERY_SECOND: + PID_Every_Second(); + break; + case FUNC_SHOW_SENSOR: + // only use this if the pid loop is to use the local sensor for pv + #if defined PID_USE_LOCAL_SENSOR + PID_Show_Sensor(); + #endif // PID_USE_LOCAL_SENSOR + break; + case FUNC_COMMAND: + result = PID_Command(); + break; + } + return result; +} + +#endif // USE_TIMEPROP From 3f9960719aaf7d20f3a1e1f63f7fc53084ddfa8a Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 4 Jan 2021 10:42:06 +0100 Subject: [PATCH 02/28] log_data => TasmotaGlobal.log_data; mqtt_data => TasmotaGlobal.mqtt_data --- tasmota/xdrv_91_timeprop.ino | 4 ++-- tasmota/xdrv_92_pid.ino | 38 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tasmota/xdrv_91_timeprop.ino b/tasmota/xdrv_91_timeprop.ino index ba3f0af4b..c9c4454fa 100644 --- a/tasmota/xdrv_91_timeprop.ino +++ b/tasmota/xdrv_91_timeprop.ino @@ -106,7 +106,7 @@ void Timeprop_Set_Power( int index, float power ) void Timeprop_Init() { - snprintf_P(log_data, sizeof(log_data), "Timeprop Init"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "Timeprop Init"); AddLog(LOG_LEVEL_INFO); int cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES}; int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES}; @@ -182,7 +182,7 @@ boolean Timeprop_Command() if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS) { timeprops[XdrvMailbox.index].setPower( atof(XdrvMailbox.data), timeprop_current_time_secs ); } - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_TIMEPROP D_CMND_TIMEPROP_SETPOWER "%d\":\"%s\"}"), + snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"" D_CMND_TIMEPROP D_CMND_TIMEPROP_SETPOWER "%d\":\"%s\"}"), XdrvMailbox.index, XdrvMailbox.data); } else { diff --git a/tasmota/xdrv_92_pid.ino b/tasmota/xdrv_92_pid.ino index e05f824e5..2ef51cc09 100644 --- a/tasmota/xdrv_92_pid.ino +++ b/tasmota/xdrv_92_pid.ino @@ -160,7 +160,7 @@ static long pid_current_time_secs = 0; // a counter that counts seconds since i void PID_Init() { - snprintf_P(log_data, sizeof(log_data), "PID Init"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID Init"); AddLog(LOG_LEVEL_INFO); pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); @@ -182,7 +182,7 @@ void PID_Show_Sensor() { // as published in tele/SENSOR // Update period is specified in TELE_PERIOD // e.g. "{"Time":"2018-03-13T16:48:05","DS18B20":{"Temperature":22.0},"TempUnit":"C"}" - snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor: mqtt_data: %s", mqtt_data); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor: mqtt_data: %s", TasmotaGlobal.mqtt_data); AddLog(LOG_LEVEL_INFO); StaticJsonBuffer<400> jsonBuffer; // force mqtt_data to read only to stop parse from overwriting it @@ -191,7 +191,7 @@ void PID_Show_Sensor() { const char* value = data_json["DS18B20"]["Temperature"]; // check that something was found and it contains a number if (value != NULL && strlen(value) > 0 && (isdigit(value[0]) || (value[0] == '-' && isdigit(value[1])) ) ) { - snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor: Temperature: %s", value); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor: Temperature: %s", value); AddLog(LOG_LEVEL_INFO); // pass the value to the pid alogorithm to use as current pv last_pv_update_secs = pid_current_time_secs; @@ -202,12 +202,12 @@ void PID_Show_Sensor() { run_pid_now = true; } } else { - snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor - no temperature found"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor - no temperature found"); AddLog(LOG_LEVEL_INFO); } } else { // parse failed - snprintf_P(log_data, sizeof(log_data), "PID_Show_Sensor - json parse failed"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor - json parse failed"); AddLog(LOG_LEVEL_INFO); } } @@ -228,7 +228,7 @@ boolean PID_Command() boolean serviced = true; uint8_t ua_prefix_len = strlen(D_CMND_PID); // to detect prefix of command - snprintf_P(log_data, sizeof(log_data), "Command called: " + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "Command called: " "index: %d data_len: %d payload: %d topic: %s data: %s", XdrvMailbox.index, XdrvMailbox.data_len, @@ -243,7 +243,7 @@ boolean PID_Command() serviced = true; switch (command_code) { case CMND_PID_SETPV: - snprintf_P(log_data, sizeof(log_data), "PID command setpv"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command setpv"); AddLog(LOG_LEVEL_INFO); last_pv_update_secs = pid_current_time_secs; pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); @@ -255,62 +255,62 @@ boolean PID_Command() break; case CMND_PID_SETSETPOINT: - snprintf_P(log_data, sizeof(log_data), "PID command setsetpoint"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command setsetpoint"); AddLog(LOG_LEVEL_INFO); pid.setSp(atof(XdrvMailbox.data)); break; case CMND_PID_SETPROPBAND: - snprintf_P(log_data, sizeof(log_data), "PID command propband"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command propband"); AddLog(LOG_LEVEL_INFO); pid.setPb(atof(XdrvMailbox.data)); break; case CMND_PID_SETINTEGRAL_TIME: - snprintf_P(log_data, sizeof(log_data), "PID command Ti"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command Ti"); AddLog(LOG_LEVEL_INFO); pid.setTi(atof(XdrvMailbox.data)); break; case CMND_PID_SETDERIVATIVE_TIME: - snprintf_P(log_data, sizeof(log_data), "PID command Td"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command Td"); AddLog(LOG_LEVEL_INFO); pid.setTd(atof(XdrvMailbox.data)); break; case CMND_PID_SETINITIAL_INT: - snprintf_P(log_data, sizeof(log_data), "PID command initial int"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command initial int"); AddLog(LOG_LEVEL_INFO); pid.setInitialInt(atof(XdrvMailbox.data)); break; case CMND_PID_SETDERIV_SMOOTH_FACTOR: - snprintf_P(log_data, sizeof(log_data), "PID command deriv smooth"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command deriv smooth"); AddLog(LOG_LEVEL_INFO); pid.setDSmooth(atof(XdrvMailbox.data)); break; case CMND_PID_SETAUTO: - snprintf_P(log_data, sizeof(log_data), "PID command auto"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command auto"); AddLog(LOG_LEVEL_INFO); pid.setAuto(atoi(XdrvMailbox.data)); break; case CMND_PID_SETMANUAL_POWER: - snprintf_P(log_data, sizeof(log_data), "PID command manual power"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command manual power"); AddLog(LOG_LEVEL_INFO); pid.setManualPower(atof(XdrvMailbox.data)); break; case CMND_PID_SETMAX_INTERVAL: - snprintf_P(log_data, sizeof(log_data), "PID command set max interval"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command set max interval"); AddLog(LOG_LEVEL_INFO); max_interval = atoi(XdrvMailbox.data); pid.setMaxInterval(max_interval); break; case CMND_PID_SETUPDATE_SECS: - snprintf_P(log_data, sizeof(log_data), "PID command set update secs"); + snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command set update secs"); AddLog(LOG_LEVEL_INFO); update_secs = atoi(XdrvMailbox.data) ; if (update_secs < 0) update_secs = 0; @@ -322,7 +322,7 @@ boolean PID_Command() if (serviced) { // set mqtt RESULT - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"), XdrvMailbox.topic, XdrvMailbox.data); + snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), XdrvMailbox.topic, XdrvMailbox.data); } } else { @@ -336,7 +336,7 @@ static void run_pid() double power = pid.tick(pid_current_time_secs); char buf[10]; dtostrfd(power, 3, buf); - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", buf); + snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", buf); MqttPublishPrefixTopic_P(TELE, "PID", false); #if defined PID_SHUTTER From 66b85b6ddf5705cc6f609fa91164d463abe7812c Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 4 Jan 2021 11:11:05 +0100 Subject: [PATCH 03/28] move to AddLog_P --- tasmota/xdrv_91_timeprop.ino | 3 +- tasmota/xdrv_92_pid.ino | 53 ++++++++++++------------------------ 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/tasmota/xdrv_91_timeprop.ino b/tasmota/xdrv_91_timeprop.ino index c9c4454fa..b9dc0f154 100644 --- a/tasmota/xdrv_91_timeprop.ino +++ b/tasmota/xdrv_91_timeprop.ino @@ -106,8 +106,7 @@ void Timeprop_Set_Power( int index, float power ) void Timeprop_Init() { - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "Timeprop Init"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("Timeprop Init")); int cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES}; int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES}; int opInverts[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_OPINVERTS}; diff --git a/tasmota/xdrv_92_pid.ino b/tasmota/xdrv_92_pid.ino index 2ef51cc09..1a3f7b59b 100644 --- a/tasmota/xdrv_92_pid.ino +++ b/tasmota/xdrv_92_pid.ino @@ -160,8 +160,7 @@ static long pid_current_time_secs = 0; // a counter that counts seconds since i void PID_Init() { - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID Init"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID Init")); pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); } @@ -182,8 +181,7 @@ void PID_Show_Sensor() { // as published in tele/SENSOR // Update period is specified in TELE_PERIOD // e.g. "{"Time":"2018-03-13T16:48:05","DS18B20":{"Temperature":22.0},"TempUnit":"C"}" - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor: mqtt_data: %s", TasmotaGlobal.mqtt_data); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor: mqtt_data: %s"), TasmotaGlobal.mqtt_data); StaticJsonBuffer<400> jsonBuffer; // force mqtt_data to read only to stop parse from overwriting it JsonObject& data_json = jsonBuffer.parseObject((const char*)mqtt_data); @@ -191,8 +189,7 @@ void PID_Show_Sensor() { const char* value = data_json["DS18B20"]["Temperature"]; // check that something was found and it contains a number if (value != NULL && strlen(value) > 0 && (isdigit(value[0]) || (value[0] == '-' && isdigit(value[1])) ) ) { - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor: Temperature: %s", value); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor: Temperature: %s"), value); // pass the value to the pid alogorithm to use as current pv last_pv_update_secs = pid_current_time_secs; pid.setPv(atof(value), last_pv_update_secs); @@ -202,13 +199,11 @@ void PID_Show_Sensor() { run_pid_now = true; } } else { - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor - no temperature found"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor - no temperature found")); } } else { // parse failed - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID_Show_Sensor - json parse failed"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor - json parse failed")); } } @@ -228,14 +223,13 @@ boolean PID_Command() boolean serviced = true; uint8_t ua_prefix_len = strlen(D_CMND_PID); // to detect prefix of command - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "Command called: " - "index: %d data_len: %d payload: %d topic: %s data: %s", + AddLog_P(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 : "")); - AddLog(LOG_LEVEL_INFO); if (0 == strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_PID), ua_prefix_len)) { // command starts with pid_ @@ -243,8 +237,7 @@ boolean PID_Command() serviced = true; switch (command_code) { case CMND_PID_SETPV: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command setpv"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command setpv")); last_pv_update_secs = pid_current_time_secs; pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); // also trigger running the pid algorithm if we have been told to run it each pv sample @@ -255,63 +248,53 @@ boolean PID_Command() break; case CMND_PID_SETSETPOINT: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command setsetpoint"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command setsetpoint")); pid.setSp(atof(XdrvMailbox.data)); break; case CMND_PID_SETPROPBAND: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command propband"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command propband")); pid.setPb(atof(XdrvMailbox.data)); break; case CMND_PID_SETINTEGRAL_TIME: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command Ti"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command Ti")); pid.setTi(atof(XdrvMailbox.data)); break; case CMND_PID_SETDERIVATIVE_TIME: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command Td"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command Td")); pid.setTd(atof(XdrvMailbox.data)); break; case CMND_PID_SETINITIAL_INT: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command initial int"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command initial int")); pid.setInitialInt(atof(XdrvMailbox.data)); break; case CMND_PID_SETDERIV_SMOOTH_FACTOR: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command deriv smooth"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command deriv smooth")); pid.setDSmooth(atof(XdrvMailbox.data)); break; case CMND_PID_SETAUTO: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command auto"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command auto")); pid.setAuto(atoi(XdrvMailbox.data)); break; case CMND_PID_SETMANUAL_POWER: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command manual power"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command manual power")); pid.setManualPower(atof(XdrvMailbox.data)); break; case CMND_PID_SETMAX_INTERVAL: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command set max interval"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command set max interval")); max_interval = atoi(XdrvMailbox.data); pid.setMaxInterval(max_interval); break; case CMND_PID_SETUPDATE_SECS: - snprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), "PID command set update secs"); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID command set update secs")); update_secs = atoi(XdrvMailbox.data) ; if (update_secs < 0) update_secs = 0; break; From a2ea85d6a1f9c51a8d06fb0624cba8f533ec33aa Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 4 Jan 2021 16:19:23 +0100 Subject: [PATCH 04/28] rewrite the interface to values from mqtt_data --- tasmota/xdrv_92_pid.ino | 48 +++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/tasmota/xdrv_92_pid.ino b/tasmota/xdrv_92_pid.ino index 1a3f7b59b..a3a10b0ce 100644 --- a/tasmota/xdrv_92_pid.ino +++ b/tasmota/xdrv_92_pid.ino @@ -182,17 +182,43 @@ void PID_Show_Sensor() { // Update period is specified in TELE_PERIOD // e.g. "{"Time":"2018-03-13T16:48:05","DS18B20":{"Temperature":22.0},"TempUnit":"C"}" AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor: mqtt_data: %s"), TasmotaGlobal.mqtt_data); - StaticJsonBuffer<400> jsonBuffer; - // force mqtt_data to read only to stop parse from overwriting it - JsonObject& data_json = jsonBuffer.parseObject((const char*)mqtt_data); - if (data_json.success()) { - const char* value = data_json["DS18B20"]["Temperature"]; + AddLog_P(LOG_LEVEL_INFO, PSTR("length of MQTT MEssage: %d"), sizeof(TasmotaGlobal.mqtt_data)); + + // I need a copy of the mqtt buffer, because otherwise the actual + // message will be truncated after this call: + // JsonParser parser( (char*)TasmotaGlobal.mqtt_data); + char mqtt_buf[sizeof(TasmotaGlobal.mqtt_data)]; + strncpy (mqtt_buf, TasmotaGlobal.mqtt_data, 300); + + JsonParser parser( (char*)mqtt_buf); + JsonParserObject root = parser.getRootObject(); + if (!root) { + AddLog_P(LOG_LEVEL_ERROR, PSTR("Invalid JSON in PID processing: %s"), mqtt_buf); + } + + // Check if there is a named entry in the json: + JsonParserToken ds18b20_entry = root[PSTR("DS18B20")]; + if (ds18b20_entry) { + // create a new object for the DS18B20 branch: + JsonParserObject ds18b20_obj = root[PSTR("DS18B20")].getObject(); + if (ds18b20_obj) { + float ds18b20_temperature = 666; // use a temperature outside the range as a default + JsonParserToken ds18b20_temperature_token = ds18b20_obj[PSTR("Temperature")]; + if (ds18b20_temperature_token) { + ds18b20_temperature = ds18b20_obj.getFloat(PSTR("Temperature"), 665); + + char the_value[10]; + dtostrfd(ds18b20_temperature, 3, the_value); + AddLog_P(LOG_LEVEL_INFO, PSTR("the_value: %s"), the_value); + } else { + AddLog_P(LOG_LEVEL_ERROR, PSTR("No Temperature found in DS18B20")); + } // check that something was found and it contains a number - if (value != NULL && strlen(value) > 0 && (isdigit(value[0]) || (value[0] == '-' && isdigit(value[1])) ) ) { - AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor: Temperature: %s"), value); + if (ds18b20_temperature < 130) { // maximal range of DS18B20 is 125 + AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor: Temperature: %f"), ds18b20_temperature); // pass the value to the pid alogorithm to use as current pv last_pv_update_secs = pid_current_time_secs; - pid.setPv(atof(value), last_pv_update_secs); + pid.setPv(ds18b20_temperature, last_pv_update_secs); // also trigger running the pid algorithm if we have been told to run it each pv sample if (update_secs == 0) { // this runs it at the next second @@ -201,7 +227,10 @@ void PID_Show_Sensor() { } else { AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor - no temperature found")); } - } else { + } else {// if no ds18b20_obj found + AddLog_P(LOG_LEVEL_ERROR, PSTR("Invalid JSON in PID processing...")); + } + } else { // no ds18b20 key found // parse failed AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor - json parse failed")); } @@ -321,6 +350,7 @@ static void run_pid() dtostrfd(power, 3, buf); snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", buf); MqttPublishPrefixTopic_P(TELE, "PID", false); + AddLog_P (LOG_LEVEL_INFO, PSTR("power: %s"), buf); #if defined PID_SHUTTER // send output as a position from 0-100 to defined shutter From 69c41d9c3dac5a2b78b801ae220184f33746a686 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 5 Jan 2021 14:15:14 +0100 Subject: [PATCH 05/28] Moved from lib/default/ to lib/lib_div/ --- lib/{default => lib_div}/ProcessControl/PID.cpp | 0 lib/{default => lib_div}/ProcessControl/PID.h | 0 lib/{default => lib_div}/ProcessControl/Timeprop.cpp | 0 lib/{default => lib_div}/ProcessControl/Timeprop.h | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename lib/{default => lib_div}/ProcessControl/PID.cpp (100%) rename lib/{default => lib_div}/ProcessControl/PID.h (100%) rename lib/{default => lib_div}/ProcessControl/Timeprop.cpp (100%) rename lib/{default => lib_div}/ProcessControl/Timeprop.h (100%) diff --git a/lib/default/ProcessControl/PID.cpp b/lib/lib_div/ProcessControl/PID.cpp similarity index 100% rename from lib/default/ProcessControl/PID.cpp rename to lib/lib_div/ProcessControl/PID.cpp diff --git a/lib/default/ProcessControl/PID.h b/lib/lib_div/ProcessControl/PID.h similarity index 100% rename from lib/default/ProcessControl/PID.h rename to lib/lib_div/ProcessControl/PID.h diff --git a/lib/default/ProcessControl/Timeprop.cpp b/lib/lib_div/ProcessControl/Timeprop.cpp similarity index 100% rename from lib/default/ProcessControl/Timeprop.cpp rename to lib/lib_div/ProcessControl/Timeprop.cpp diff --git a/lib/default/ProcessControl/Timeprop.h b/lib/lib_div/ProcessControl/Timeprop.h similarity index 100% rename from lib/default/ProcessControl/Timeprop.h rename to lib/lib_div/ProcessControl/Timeprop.h From 20c9f2f169b6664277fdd1e37510e32a73a05bc2 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 5 Jan 2021 15:42:09 +0100 Subject: [PATCH 06/28] add config statements for pid and timeprop --- tasmota/my_user_config.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index fb9c30774..f357a7ad0 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -811,6 +811,11 @@ // -- Prometheus exporter --------------------------- //#define USE_PROMETHEUS // Add support for https://prometheus.io/ metrics exporting over HTTP /metrics endpoint +// -- PID and Timeprop ------------------------------ +// #define use TIMEPROP // Add support for the timeprop feature (+9k2 code) + // For details on the configuration please see the header of tasmota/xdrv_91_timeprop.ino +// #define USE_PID // Add suport for the PID feature (+11k5 code) + // For details on the configuration please see the header of tasmota/xdrv_92_pid.ino // -- End of general directives ------------------- /*********************************************************************************************\ From 6f083aa944de4b973731ce46c3e121d9663ce409 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 16:36:24 +0100 Subject: [PATCH 07/28] boolean -> bool, Prefix Addlog --- tasmota/xdrv_91_timeprop.ino | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tasmota/xdrv_91_timeprop.ino b/tasmota/xdrv_91_timeprop.ino index b9dc0f154..ad554c422 100644 --- a/tasmota/xdrv_91_timeprop.ino +++ b/tasmota/xdrv_91_timeprop.ino @@ -106,7 +106,7 @@ void Timeprop_Set_Power( int index, float power ) void Timeprop_Init() { - AddLog_P(LOG_LEVEL_INFO, PSTR("Timeprop Init")); + AddLog_P(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}; @@ -148,10 +148,10 @@ void Timeprop_Xdrv_Power() { /* } XdrvMailbox; */ // To get here post with topic cmnd/timeprop_setpower_n where n is index into timeprops 0:7 -boolean Timeprop_Command() +bool Timeprop_Command() { char command [CMDSZ]; - boolean serviced = true; + bool serviced = true; uint8_t ua_prefix_len = strlen(D_CMND_TIMEPROP); // to detect prefix of command /* snprintf_P(log_data, sizeof(log_data), "Command called: " @@ -199,10 +199,10 @@ boolean Timeprop_Command() #define XDRV_91 91 -boolean Xdrv91(byte function) -//boolean XDRV_91(byte function) +bool Xdrv91(byte function) +//bool XDRV_91(byte function) { - boolean result = false; + bool result = false; switch (function) { case FUNC_INIT: From 6fded78bc50b1a8a1217b53c15a3e3fc1eb3bf95 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 16:37:11 +0100 Subject: [PATCH 08/28] boolean -> bool, Prefix Addlog; simplify incoming temperature handling --- tasmota/xdrv_92_pid.ino | 118 ++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 72 deletions(-) diff --git a/tasmota/xdrv_92_pid.ino b/tasmota/xdrv_92_pid.ino index a3a10b0ce..2cc2253df 100644 --- a/tasmota/xdrv_92_pid.ino +++ b/tasmota/xdrv_92_pid.ino @@ -113,9 +113,11 @@ // If not using the sensor then you can supply process values via MQTT using // cmnd pid_pv - //#define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the + #define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the // number of the shutter here. Otherwise leave this commented out + #define PID_DEBUGGING // Increase number of log messages + * Help with using the PID algorithm and with loop tuning can be found at * http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html * This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on @@ -154,13 +156,15 @@ static PID pid; static int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run static int max_interval = PID_MAX_INTERVAL; static unsigned long last_pv_update_secs = 0; -static boolean run_pid_now = false; // tells PID_Every_Second to run the pid algorithm +static bool run_pid_now = false; // tells PID_Every_Second to run the pid algorithm static long pid_current_time_secs = 0; // a counter that counts seconds since initialisation void PID_Init() { - AddLog_P(LOG_LEVEL_INFO, PSTR("PID Init")); + #ifdef PID_DEBUGGING + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: Init")); + #endif // PID_DEBUGGING pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); } @@ -180,63 +184,32 @@ void PID_Show_Sensor() { // Called each time new sensor data available, data in mqtt data in same format // as published in tele/SENSOR // Update period is specified in TELE_PERIOD - // e.g. "{"Time":"2018-03-13T16:48:05","DS18B20":{"Temperature":22.0},"TempUnit":"C"}" - AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor: mqtt_data: %s"), TasmotaGlobal.mqtt_data); - AddLog_P(LOG_LEVEL_INFO, PSTR("length of MQTT MEssage: %d"), sizeof(TasmotaGlobal.mqtt_data)); + if (!isnan(TasmotaGlobal.temperature_celsius)) { + const float ds18b20_temperature = TasmotaGlobal.temperature_celsius; - // I need a copy of the mqtt buffer, because otherwise the actual - // message will be truncated after this call: - // JsonParser parser( (char*)TasmotaGlobal.mqtt_data); - char mqtt_buf[sizeof(TasmotaGlobal.mqtt_data)]; - strncpy (mqtt_buf, TasmotaGlobal.mqtt_data, 300); + #define marcus_debug + #ifdef marcus_debug + char the_value[10]; + dtostrfd(ds18b20_temperature, 3, the_value); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: the_value: %s"), the_value); + #endif marcus_debug - JsonParser parser( (char*)mqtt_buf); - JsonParserObject root = parser.getRootObject(); - if (!root) { - AddLog_P(LOG_LEVEL_ERROR, PSTR("Invalid JSON in PID processing: %s"), mqtt_buf); - } - - // Check if there is a named entry in the json: - JsonParserToken ds18b20_entry = root[PSTR("DS18B20")]; - if (ds18b20_entry) { - // create a new object for the DS18B20 branch: - JsonParserObject ds18b20_obj = root[PSTR("DS18B20")].getObject(); - if (ds18b20_obj) { - float ds18b20_temperature = 666; // use a temperature outside the range as a default - JsonParserToken ds18b20_temperature_token = ds18b20_obj[PSTR("Temperature")]; - if (ds18b20_temperature_token) { - ds18b20_temperature = ds18b20_obj.getFloat(PSTR("Temperature"), 665); - - char the_value[10]; - dtostrfd(ds18b20_temperature, 3, the_value); - AddLog_P(LOG_LEVEL_INFO, PSTR("the_value: %s"), the_value); - } else { - AddLog_P(LOG_LEVEL_ERROR, PSTR("No Temperature found in DS18B20")); - } - // check that something was found and it contains a number - if (ds18b20_temperature < 130) { // maximal range of DS18B20 is 125 - AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor: Temperature: %f"), ds18b20_temperature); - // pass the value to the pid alogorithm to use as current pv - last_pv_update_secs = pid_current_time_secs; - pid.setPv(ds18b20_temperature, last_pv_update_secs); - // also trigger running the pid algorithm if we have been told to run it each pv sample - if (update_secs == 0) { - // this runs it at the next second - run_pid_now = true; - } - } else { - AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor - no temperature found")); - } - } else {// if no ds18b20_obj found - AddLog_P(LOG_LEVEL_ERROR, PSTR("Invalid JSON in PID processing...")); - } - } else { // no ds18b20 key found - // parse failed - AddLog_P(LOG_LEVEL_INFO, PSTR("PID_Show_Sensor - json parse failed")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: PID_Show_Sensor: Temperature: %f"), ds18b20_temperature); + // pass the value to the pid alogorithm to use as current pv + last_pv_update_secs = pid_current_time_secs; + pid.setPv(ds18b20_temperature, last_pv_update_secs); + // also trigger running the pid algorithm if we have been told to run it each pv sample + if (update_secs == 0) { + // this runs it at the next second + run_pid_now = true; + } + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: No Temperature found")); } } + /* struct XDRVMAILBOX { */ /* uint16_t valid; */ /* uint16_t index; */ @@ -246,13 +219,13 @@ void PID_Show_Sensor() { /* char *data; */ /* } XdrvMailbox; */ -boolean PID_Command() +bool PID_Command() { char command [CMDSZ]; - boolean serviced = true; + bool serviced = true; uint8_t ua_prefix_len = strlen(D_CMND_PID); // to detect prefix of command - AddLog_P(LOG_LEVEL_INFO, PSTR("Command called: " + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: Command called: " "index: %d data_len: %d payload: %d topic: %s data: %s"), XdrvMailbox.index, XdrvMailbox.data_len, @@ -266,7 +239,7 @@ boolean PID_Command() serviced = true; switch (command_code) { case CMND_PID_SETPV: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command setpv")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command setpv")); last_pv_update_secs = pid_current_time_secs; pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); // also trigger running the pid algorithm if we have been told to run it each pv sample @@ -277,53 +250,53 @@ boolean PID_Command() break; case CMND_PID_SETSETPOINT: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command setsetpoint")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command setsetpoint")); pid.setSp(atof(XdrvMailbox.data)); break; case CMND_PID_SETPROPBAND: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command propband")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command propband")); pid.setPb(atof(XdrvMailbox.data)); break; case CMND_PID_SETINTEGRAL_TIME: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command Ti")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command Ti")); pid.setTi(atof(XdrvMailbox.data)); break; case CMND_PID_SETDERIVATIVE_TIME: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command Td")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command Td")); pid.setTd(atof(XdrvMailbox.data)); break; case CMND_PID_SETINITIAL_INT: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command initial int")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command initial int")); pid.setInitialInt(atof(XdrvMailbox.data)); break; case CMND_PID_SETDERIV_SMOOTH_FACTOR: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command deriv smooth")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command deriv smooth")); pid.setDSmooth(atof(XdrvMailbox.data)); break; case CMND_PID_SETAUTO: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command auto")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command auto")); pid.setAuto(atoi(XdrvMailbox.data)); break; case CMND_PID_SETMANUAL_POWER: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command manual power")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command manual power")); pid.setManualPower(atof(XdrvMailbox.data)); break; case CMND_PID_SETMAX_INTERVAL: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command set max interval")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command set max interval")); max_interval = atoi(XdrvMailbox.data); pid.setMaxInterval(max_interval); break; case CMND_PID_SETUPDATE_SECS: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID command set update secs")); + AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command set update secs")); update_secs = atoi(XdrvMailbox.data) ; if (update_secs < 0) update_secs = 0; break; @@ -335,6 +308,7 @@ boolean PID_Command() if (serviced) { // set mqtt RESULT snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), XdrvMailbox.topic, XdrvMailbox.data); + Response_P("Hello world of results"); } } else { @@ -350,7 +324,7 @@ static void run_pid() dtostrfd(power, 3, buf); snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", buf); MqttPublishPrefixTopic_P(TELE, "PID", false); - AddLog_P (LOG_LEVEL_INFO, PSTR("power: %s"), buf); + AddLog_P (LOG_LEVEL_INFO, PSTR("PID: power: %s"), buf); #if defined PID_SHUTTER // send output as a position from 0-100 to defined shutter @@ -370,10 +344,10 @@ static void run_pid() #define XDRV_92 92 -boolean Xdrv92(byte function) -//boolean XDRV_92(byte function) +bool Xdrv92(byte function) +//bool XDRV_92(byte function) { - boolean result = false; + bool result = false; switch (function) { case FUNC_INIT: From 92402cda2976b944cb1eb35d28a7b261bbbb6315 Mon Sep 17 00:00:00 2001 From: Adrian Scillato <35405447+ascillato@users.noreply.github.com> Date: Wed, 6 Jan 2021 16:57:00 -0300 Subject: [PATCH 09/28] Update Spanish Translation --- tasmota/language/es_ES.h | 56 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index 6d0942023..01b323d94 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -28,7 +28,7 @@ * Use online command StateText to translate ON, OFF, HOLD and TOGGLE. * Use online command Prefix to translate cmnd, stat and tele. * - * Updated until v9.1.0.2 + * Updated until v9.2.0.3 \*********************************************************************/ #define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English) @@ -461,30 +461,30 @@ #define D_KNX_RX_SCENE "KNX ESCENA RX" // xdrv_23_zigbee -#define D_ZIGBEE_PERMITJOIN_ACTIVE "Devices allowed to join" -#define D_ZIGBEE_MAPPING_TITLE "Tasmota Zigbee Mapping" -#define D_ZIGBEE_NOT_STARTED "Zigbee not started" -#define D_ZIGBEE_MAPPING_IN_PROGRESS_SEC "Mapping in progress (%d s. remaining)" -#define D_ZIGBEE_MAPPING_NOT_PRESENT "No mapping" -#define D_ZIGBEE_MAP_REFRESH "Zigbee Map Refresh" -#define D_ZIGBEE_MAP "Zigbee Map" -#define D_ZIGBEE_PERMITJOIN "Zigbee Permit Join" -#define D_ZIGBEE_GENERATE_KEY "generating random Zigbee network key" -#define D_ZIGBEE_UNKNOWN_DEVICE "Unknown device" -#define D_ZIGBEE_UNKNOWN_ATTRIBUTE "Unknown attribute" -#define D_ZIGBEE_INVALID_PARAM "Invalid parameter" -#define D_ZIGBEE_MISSING_PARAM "Missing parameters" -#define D_ZIGBEE_UNKNWON_ATTRIBUTE "Unknown attribute name (ignored): %s" -#define D_ZIGBEE_TOO_MANY_CLUSTERS "No more than one cluster id per command" -#define D_ZIGBEE_WRONG_DELIMITER "Wrong delimiter for payload" -#define D_ZIGBEE_UNRECOGNIZED_COMMAND "Unrecognized zigbee command: %s" -#define D_ZIGBEE_TOO_MANY_COMMANDS "Only 1 command allowed (%d)" -#define D_ZIGBEE_NO_ATTRIBUTE "No attribute in list" -#define D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE "Unsupported attribute type" -#define D_ZIGBEE_JSON_REQUIRED "Config requires JSON objects" -#define D_ZIGBEE_RESET_1_OR_2 "1 or 2 to reset" -#define D_ZIGBEE_EEPROM_FOUND_AT_ADDRESS "ZBBridge EEPROM found at address" -#define D_ZIGBEE_RANDOMIZING_ZBCONFIG "Randomizing Zigbee parameters, please check with 'ZbConfig'" +#define D_ZIGBEE_PERMITJOIN_ACTIVE "Dispositivos permitidos a unirse" +#define D_ZIGBEE_MAPPING_TITLE "Mapeo de Tasmota Zigbee" +#define D_ZIGBEE_NOT_STARTED "Zigbee no iniciado" +#define D_ZIGBEE_MAPPING_IN_PROGRESS_SEC "Mapeo en progreso (%d s. restantes)" +#define D_ZIGBEE_MAPPING_NOT_PRESENT "Sin mapeo" +#define D_ZIGBEE_MAP_REFRESH "Actualizar Mapa Zigbee" +#define D_ZIGBEE_MAP "Mapa Zigbee" +#define D_ZIGBEE_PERMITJOIN "Permitir unirse a Zigbee" +#define D_ZIGBEE_GENERATE_KEY "Generando una clave aleatoria de red Zigbee" +#define D_ZIGBEE_UNKNOWN_DEVICE "Dispositivo desconocido" +#define D_ZIGBEE_UNKNOWN_ATTRIBUTE "Atributo desconocido" +#define D_ZIGBEE_INVALID_PARAM "Parámetro inválido" +#define D_ZIGBEE_MISSING_PARAM "Parámetros faltantes" +#define D_ZIGBEE_UNKNWON_ATTRIBUTE "Nombre de atributo desconocido (ignorado): %s" +#define D_ZIGBEE_TOO_MANY_CLUSTERS "No mas de un id de cluster por comando" +#define D_ZIGBEE_WRONG_DELIMITER "Delimitador incorrecto para payload" +#define D_ZIGBEE_UNRECOGNIZED_COMMAND "Comando zigbee no reconocido: %s" +#define D_ZIGBEE_TOO_MANY_COMMANDS "Solo un comando es permitido (%d)" +#define D_ZIGBEE_NO_ATTRIBUTE "No hay atributos en la lista" +#define D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE "Atributo no soportado" +#define D_ZIGBEE_JSON_REQUIRED "La configuración debe ser en JSON" +#define D_ZIGBEE_RESET_1_OR_2 "1 o 2 para resetear" +#define D_ZIGBEE_EEPROM_FOUND_AT_ADDRESS "Encontrada EEPROM de ZBBridge en" +#define D_ZIGBEE_RANDOMIZING_ZBCONFIG "Configurando parámetros Zigbee de forma aleatoria. Usar 'ZbConfig' para revisarlos." // xdrv_03_energy.ino #define D_ENERGY_TODAY "Energía Hoy" @@ -875,9 +875,9 @@ #define D_SCRIPT_UPLOAD_FILES "Cargar Archivos" //xdrv_50_filesystem.ino -#define D_MANAGE_FILE_SYSTEM "Manage File system" -#define D_FS_SIZE "Size" -#define D_FS_FREE "Free" +#define D_MANAGE_FILE_SYSTEM "Explorar Archivos" +#define D_FS_SIZE "Tamaño" +#define D_FS_FREE "Libre" //xsns_67_as3935.ino #define D_AS3935_GAIN "Ganancia:" From 89c3c44754c166fb156a416d2b89bfe5455782b7 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 21:19:39 +0100 Subject: [PATCH 10/28] add get functions --- lib/lib_div/ProcessControl/PID.cpp | 41 ++++++++++++++++++++++++++++++ lib/lib_div/ProcessControl/PID.h | 11 ++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/lib_div/ProcessControl/PID.cpp b/lib/lib_div/ProcessControl/PID.cpp index 0a8e35356..3e5298087 100644 --- a/lib/lib_div/ProcessControl/PID.cpp +++ b/lib/lib_div/ProcessControl/PID.cpp @@ -191,3 +191,44 @@ void PID::setManualPower( double manual_op ) { void PID::setMaxInterval( int max_interval ) { m_max_interval = max_interval; } + + +double PID::getPv() { + return(m_pv); +} + +double PID::getSp() { + return(m_setpoint); +} + +double PID::getPb() { + return(m_prop_band); +} + +double PID::getTi() { + return(m_t_integral); +} + +double PID::getTd() { + return(m_t_derivative); +} + +double PID::getInitialInt() { + return(m_integral_default); +} + +double PID::getDSmooth() { + return(m_smooth_factor); +} + +unsigned char PID::getAuto() { + return(m_mode_auto); +} + +double PID::getManualPower() { + return(m_manual_op); +} + +int PID::getMaxInterval() { + return(m_max_interval); +} diff --git a/lib/lib_div/ProcessControl/PID.h b/lib/lib_div/ProcessControl/PID.h index 3409da4a2..6ffa9a648 100644 --- a/lib/lib_div/ProcessControl/PID.h +++ b/lib/lib_div/ProcessControl/PID.h @@ -65,6 +65,17 @@ public: void setManualPower( double manual_op ); void setMaxInterval( int max_interval ); + double getPv(); + double getSp(); + double getPb(); + double getTi(); + double getTd(); + double getInitialInt(); + double getDSmooth(); + unsigned char getAuto(); + double getManualPower(); + int getMaxInterval(); + private: double m_pv; double m_setpoint; From bedfc4170ebdf0fc8b603e0540c01cd12e96a252 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Wed, 6 Jan 2021 21:19:43 +0100 Subject: [PATCH 11/28] Update de_DE.h --- tasmota/language/de_DE.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index bd65cb69e..e397d4bad 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -28,7 +28,7 @@ * Use online command StateText to translate ON, OFF, HOLD and TOGGLE. * Use online command Prefix to translate cmnd, stat and tele. * - * Updated until v9.2.0.1 + * Updated until v9.2.0.3 \*********************************************************************/ //#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English) @@ -875,9 +875,9 @@ #define D_SCRIPT_UPLOAD_FILES "Upload Dateien" //xdrv_50_filesystem.ino -#define D_MANAGE_FILE_SYSTEM "Manage File system" -#define D_FS_SIZE "Size" -#define D_FS_FREE "Free" +#define D_MANAGE_FILE_SYSTEM "Verwalte Dateisystem" +#define D_FS_SIZE "Größe" +#define D_FS_FREE "Frei" //xsns_67_as3935.ino #define D_AS3935_GAIN "Umgebung:" From dab488229f30a8ad28ffdf68b82353c50c195734 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:00:33 +0100 Subject: [PATCH 12/28] rename variables pid_xy -> PidXy; use Response_P; use DecodeCommand; remove logging --- tasmota/xdrv_92_pid.ino | 337 +++++++++++++++++++++++----------------- 1 file changed, 194 insertions(+), 143 deletions(-) diff --git a/tasmota/xdrv_92_pid.ino b/tasmota/xdrv_92_pid.ino index 2cc2253df..a887ae8f8 100644 --- a/tasmota/xdrv_92_pid.ino +++ b/tasmota/xdrv_92_pid.ino @@ -27,14 +27,14 @@ #define USE_PID // include the pid feature (+4.3k) #define PID_SETPOINT 19.5 // Setpoint value. This is the process value that the process is // aiming for. - // May be adjusted via MQTT using cmnd pid_sp + // May be adjusted via MQTT using cmnd PidSp #define PID_PROPBAND 5 // Proportional band in process units (eg degrees). This controls // the gain of the loop and is the range of process value over which // the power output will go from 0 to full power. The units are that // of the process and setpoint, so for example in a heating // application it might be set to 1.5 degrees. - // May be adjusted via MQTT using cmnd pid_pb + // May be adjusted via MQTT using cmnd PidPb #define PID_INTEGRAL_TIME 1800 // Integral time seconds. This is a setting for the integral time, // in seconds. It represents the time constant of the integration @@ -43,7 +43,7 @@ // example for a domestic room heated by convection radiators a setting // of one hour might be appropriate (in seconds). To disable the // integral effect set this to a large number. - // May be adjusted via MQTT using cmnd pid_ti + // May be adjusted via MQTT using cmnd PidTi #define PID_DERIVATIVE_TIME 15 // Derivative time seconds. This is a setting for the derivative time, // in seconds. It represents the time constant of the derivative effect. @@ -53,7 +53,7 @@ // disable the derivative effect set this to 0. When initially tuning a // loop it is often sensible to start with derivative zero and wind it in // once other parameters have been setup. - // May be adjusted via MQTT using cmnd pid_td + // May be adjusted via MQTT using cmnd PidTd #define PID_INITIAL_INT 0.5 // Initial integral value (0:1). This is an initial value which is used // to preset the integrated error value when the flow is deployed in @@ -69,7 +69,7 @@ // that might prevent the node from being supplied with a process value. // If no new process value is received for this time then the power is set // to the value defined for PID_MANUAL_POWER. - // May be adjusted via MQTT using cmnd pid_max_interval + // May be adjusted via MQTT using cmnd PidMaxInterval #define PID_DERIV_SMOOTH_FACTOR 3 // In situations where the process sensor has limited resolution (such as // the DS18B20), the use of deriviative can be problematic as when the @@ -82,16 +82,16 @@ // noisy. The smaller the value the greater the filtering effect but the // more it will reduce the effectiveness of the derivative. A value of zero // disables this feature. - // May be adjusted via MQTT using cmnd pid_d_smooth + // May be adjusted via MQTT using cmnd PidDSmooth #define PID_AUTO 1 // Auto mode 1 or 0 (for manual). This can be used to enable or disable // the control (1=enable, auto mode, 0=disabled, manual mode). When in // manual mode the output is set the value definded for PID_MANUAL_POWER - // May be adjusted via MQTT using cmnd pid_auto + // May be adjusted via MQTT using cmnd PidAuto #define PID_MANUAL_POWER 0 // Power output when in manual mode or fallback mode if too long elapses // between process values - // May be adjusted via MQTT using cmnd pid_manual_power + // May be adjusted via MQTT using cmnd PidManualPower #define PID_UPDATE_SECS 0 // How often to run the pid algorithm (integer secs) or 0 to run the algorithm // each time a new pv value is received, for most applictions specify 0. @@ -99,7 +99,7 @@ // that is short compared to the response of the process. For example, // something like 15 seconds may well be appropriate for a domestic room // heating application. - // May be adjusted via MQTT using cmnd pid_update_secs + // May be adjusted via MQTT using cmnd PidUpdateSecs #define PID_USE_TIMPROP 1 // To use an internal relay for a time proportioned output to drive the // process, set this to indicate which timeprop output to use. For a device @@ -108,16 +108,24 @@ // explained in xdrv_91_timeprop.ino // To disable this feature leave this undefined (undefined, not defined to nothing). - #define PID_USE_LOCAL_SENSOR // if defined then the local sensor will be used for pv. Leave undefined if + #define PID_USE_LOCAL_SENSOR // If defined then the local sensor will be used for pv. Leave undefined if // this is not required. The rate that the sensor is read is defined by TELE_PERIOD // If not using the sensor then you can supply process values via MQTT using - // cmnd pid_pv + // cmnd PidPv #define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the // number of the shutter here. Otherwise leave this commented out #define PID_DEBUGGING // Increase number of log messages + #define PID_REPORT_SETTINGS // If defined, the SENSOR output will provide more extensive json + // output in the PID section + +// #define PID_BACKWARD_COMPATIBLE // Preserve the backward compatible reporting of PID power via + // `%topic%/PID {"power":"0.000"}` This is now available in + // `%topic$/SENSOR {..., "PID":{"PidPower":0.00}}` + // Don't use unless you know that you need it + * Help with using the PID algorithm and with loop tuning can be found at * http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html * This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on @@ -131,26 +139,47 @@ #include "PID.h" -#define D_CMND_PID "pid_" +/* This might need to go to i18n.h */ +#define D_PRFX_PID "Pid" +#define D_CMND_PID_SETPV "Pv" +#define D_CMND_PID_SETSETPOINT "Sp" +#define D_CMND_PID_SETPROPBAND "Pb" +#define D_CMND_PID_SETINTEGRAL_TIME "Ti" +#define D_CMND_PID_SETDERIVATIVE_TIME "Td" +#define D_CMND_PID_SETINITIAL_INT "Initint" +#define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "DSmooth" +#define D_CMND_PID_SETAUTO "Auto" +#define D_CMND_PID_SETMANUAL_POWER "ManualPower" +#define D_CMND_PID_SETMAX_INTERVAL "MaxInterval" +#define D_CMND_PID_SETUPDATE_SECS "UpdateSecs" -#define D_CMND_PID_SETPV "pv" -#define D_CMND_PID_SETSETPOINT "sp" -#define D_CMND_PID_SETPROPBAND "pb" -#define D_CMND_PID_SETINTEGRAL_TIME "ti" -#define D_CMND_PID_SETDERIVATIVE_TIME "td" -#define D_CMND_PID_SETINITIAL_INT "initint" -#define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "d_smooth" -#define D_CMND_PID_SETAUTO "auto" -#define D_CMND_PID_SETMANUAL_POWER "manual_power" -#define D_CMND_PID_SETMAX_INTERVAL "max_interval" -#define D_CMND_PID_SETUPDATE_SECS "update_secs" +const char kPIDCommands[] PROGMEM = D_PRFX_PID "|" // Prefix + D_CMND_PID_SETPV "|" + D_CMND_PID_SETSETPOINT "|" + D_CMND_PID_SETPROPBAND "|" + D_CMND_PID_SETINTEGRAL_TIME "|" + D_CMND_PID_SETDERIVATIVE_TIME "|" + D_CMND_PID_SETINITIAL_INT "|" + D_CMND_PID_SETDERIV_SMOOTH_FACTOR "|" + D_CMND_PID_SETAUTO "|" + D_CMND_PID_SETMANUAL_POWER "|" + D_CMND_PID_SETMAX_INTERVAL "|" + D_CMND_PID_SETUPDATE_SECS; + ; -enum PIDCommands { CMND_PID_SETPV, CMND_PID_SETSETPOINT, CMND_PID_SETPROPBAND, CMND_PID_SETINTEGRAL_TIME, - CMND_PID_SETDERIVATIVE_TIME, CMND_PID_SETINITIAL_INT, CMND_PID_SETDERIV_SMOOTH_FACTOR, CMND_PID_SETAUTO, - CMND_PID_SETMANUAL_POWER, CMND_PID_SETMAX_INTERVAL, CMND_PID_SETUPDATE_SECS }; -const char kPIDCommands[] PROGMEM = D_CMND_PID_SETPV "|" D_CMND_PID_SETSETPOINT "|" D_CMND_PID_SETPROPBAND "|" - D_CMND_PID_SETINTEGRAL_TIME "|" D_CMND_PID_SETDERIVATIVE_TIME "|" D_CMND_PID_SETINITIAL_INT "|" D_CMND_PID_SETDERIV_SMOOTH_FACTOR "|" - D_CMND_PID_SETAUTO "|" D_CMND_PID_SETMANUAL_POWER "|" D_CMND_PID_SETMAX_INTERVAL "|" D_CMND_PID_SETUPDATE_SECS; +void (* const PIDCommand[])(void) PROGMEM = { + &CmndSetPv, + &CmndSetSp, + &CmndSetPb, + &CmndSetTi, + &cmndsetTd, + &CmndSetInitialInt, + &CmndSetDSmooth, + &CmndSetAuto, + &CmndSetManualPower, + &CmndSetMaxInterval, + &CmndSetUpdateSecs + }; static PID pid; static int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run @@ -162,9 +191,6 @@ static long pid_current_time_secs = 0; // a counter that counts seconds since i void PID_Init() { - #ifdef PID_DEBUGGING - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: Init")); - #endif // PID_DEBUGGING pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); } @@ -185,31 +211,21 @@ void PID_Show_Sensor() { // as published in tele/SENSOR // Update period is specified in TELE_PERIOD if (!isnan(TasmotaGlobal.temperature_celsius)) { - const float ds18b20_temperature = TasmotaGlobal.temperature_celsius; + const float temperature = TasmotaGlobal.temperature_celsius; - #define marcus_debug - #ifdef marcus_debug - char the_value[10]; - dtostrfd(ds18b20_temperature, 3, the_value); - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: the_value: %s"), the_value); - #endif marcus_debug - - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: PID_Show_Sensor: Temperature: %f"), ds18b20_temperature); // pass the value to the pid alogorithm to use as current pv last_pv_update_secs = pid_current_time_secs; - pid.setPv(ds18b20_temperature, last_pv_update_secs); + pid.setPv(temperature, last_pv_update_secs); // also trigger running the pid algorithm if we have been told to run it each pv sample if (update_secs == 0) { // this runs it at the next second run_pid_now = true; } } else { - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: No Temperature found")); + AddLog_P(LOG_LEVEL_ERROR, PSTR("PID: No local temperature sensor found")); } } - - /* struct XDRVMAILBOX { */ /* uint16_t valid; */ /* uint16_t index; */ @@ -219,112 +235,144 @@ void PID_Show_Sensor() { /* char *data; */ /* } XdrvMailbox; */ -bool PID_Command() -{ - char command [CMDSZ]; - bool serviced = true; - uint8_t ua_prefix_len = strlen(D_CMND_PID); // to detect prefix of command - - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: 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_PID), ua_prefix_len)) { - // command starts with pid_ - int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + ua_prefix_len, kPIDCommands); - serviced = true; - switch (command_code) { - case CMND_PID_SETPV: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command setpv")); - last_pv_update_secs = pid_current_time_secs; - pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); - // also trigger running the pid algorithm if we have been told to run it each pv sample - if (update_secs == 0) { - // this runs it at the next second - run_pid_now = true; - } - break; - - case CMND_PID_SETSETPOINT: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command setsetpoint")); - pid.setSp(atof(XdrvMailbox.data)); - break; - - case CMND_PID_SETPROPBAND: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command propband")); - pid.setPb(atof(XdrvMailbox.data)); - break; - - case CMND_PID_SETINTEGRAL_TIME: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command Ti")); - pid.setTi(atof(XdrvMailbox.data)); - break; - - case CMND_PID_SETDERIVATIVE_TIME: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command Td")); - pid.setTd(atof(XdrvMailbox.data)); - break; - - case CMND_PID_SETINITIAL_INT: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command initial int")); - pid.setInitialInt(atof(XdrvMailbox.data)); - break; - - case CMND_PID_SETDERIV_SMOOTH_FACTOR: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command deriv smooth")); - pid.setDSmooth(atof(XdrvMailbox.data)); - break; - - case CMND_PID_SETAUTO: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command auto")); - pid.setAuto(atoi(XdrvMailbox.data)); - break; - - case CMND_PID_SETMANUAL_POWER: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command manual power")); - pid.setManualPower(atof(XdrvMailbox.data)); - break; - - case CMND_PID_SETMAX_INTERVAL: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command set max interval")); - max_interval = atoi(XdrvMailbox.data); - pid.setMaxInterval(max_interval); - break; - - case CMND_PID_SETUPDATE_SECS: - AddLog_P(LOG_LEVEL_INFO, PSTR("PID: command set update secs")); - update_secs = atoi(XdrvMailbox.data) ; - if (update_secs < 0) update_secs = 0; - break; - - default: - serviced = false; +void CmndSetPv(void) { + last_pv_update_secs = pid_current_time_secs; + pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); + // also trigger running the pid algorithm if we have been told to run it each pv sample + if (update_secs == 0) { + // this runs it at the next second + run_pid_now = true; } +} - if (serviced) { - // set mqtt RESULT - snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), XdrvMailbox.topic, XdrvMailbox.data); - Response_P("Hello world of results"); - } +void CmndSetSp(void) { + pid.setSp(atof(XdrvMailbox.data)); + ResponseCmndNumber(atof(XdrvMailbox.data)); +} - } else { - serviced = false; - } - return serviced; +void CmndSetPb(void) { + pid.setPb(atof(XdrvMailbox.data)); + ResponseCmndNumber(atof(XdrvMailbox.data)); +} + +void CmndSetTi(void) { + pid.setTi(atof(XdrvMailbox.data)); + ResponseCmndNumber(atof(XdrvMailbox.data)); +} + +void cmndsetTd(void) { + pid.setTd(atof(XdrvMailbox.data)); + ResponseCmndNumber(atof(XdrvMailbox.data)); +} + +void CmndSetInitialInt(void) { + pid.setInitialInt(atof(XdrvMailbox.data)); + ResponseCmndNumber(atof(XdrvMailbox.data)); +} + +void CmndSetDSmooth(void) { + pid.setDSmooth(atof(XdrvMailbox.data)); + ResponseCmndNumber(atof(XdrvMailbox.data)); +} + +void CmndSetAuto(void) { + pid.setAuto(atoi(XdrvMailbox.data)); + ResponseCmndNumber(atoi(XdrvMailbox.data)); +} + +void CmndSetManualPower(void) { + pid.setManualPower(atof(XdrvMailbox.data)); + ResponseCmndNumber(atof(XdrvMailbox.data)); +} + +void CmndSetMaxInterval(void) { + pid.setMaxInterval(atoi(XdrvMailbox.data)); + ResponseCmndNumber(atoi(XdrvMailbox.data)); +} + +// case CMND_PID_SETUPDATE_SECS: +// update_secs = atoi(XdrvMailbox.data) ; +// if (update_secs < 0) +// update_secs = 0; +void CmndSetUpdateSecs(void) { + update_secs = (atoi(XdrvMailbox.data)); + if (update_secs < 0) + update_secs = 0; + ResponseCmndNumber(update_secs); +} + +void PIDShowValues(void) { + char str_buf[FLOATSZ]; + char chr_buf; + int i_buf; + double d_buf; + ResponseAppend_P(PSTR(",\"PID\":{")); + +// #define D_CMND_PID_SETPV "Pv" + d_buf = pid.getPv(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidPv\":%s,"), str_buf); +// #define D_CMND_PID_SETSETPOINT "Sp" + d_buf = pid.getSp(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidSp\":%s,"), str_buf); + +#ifdef PID_REPORT_MORE_SETTINGS +// #define D_CMND_PID_SETPROPBAND "Pb" + d_buf = pid.getPb(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidPb\":%s,"), str_buf); +// #define D_CMND_PID_SETINTEGRAL_TIME "Ti" + d_buf = pid.getTi(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidTi\":%s,"), str_buf); +// #define D_CMND_PID_SETDERIVATIVE_TIME "Td" + d_buf = pid.getTd(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidTd\":%s,"), str_buf); +// #define D_CMND_PID_SETINITIAL_INT "Initint" + d_buf = pid.getInitialInt(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidInitialInt\":%s,"), str_buf); +// #define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "DSmooth" + d_buf = pid.getDSmooth(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidDSmooth\":%s,"), str_buf); +// #define D_CMND_PID_SETAUTO "Auto" + chr_buf = pid.getAuto(); + ResponseAppend_P(PSTR("\"PidAuto\":%d,"), chr_buf); +// #define D_CMND_PID_SETMANUAL_POWER "ManualPower" + d_buf = pid.getManualPower(); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidManualPower\":%s,"), str_buf); +// #define D_CMND_PID_SETMAX_INTERVAL "MaxInterval" + i_buf = pid.getMaxInterval(); + ResponseAppend_P(PSTR("\"PidMaxInterval\":%d,"), i_buf); + +// #define D_CMND_PID_SETUPDATE_SECS "UpdateSecs" + ResponseAppend_P(PSTR("\"PidUpdateSecs\":%d,"), update_secs); +#endif // PID_REPORT_MORE_SETTINGS + +// The actual power value + d_buf = pid.tick(pid_current_time_secs); + dtostrfd(d_buf, 2, str_buf); + ResponseAppend_P(PSTR("\"PidPower\":%s"), str_buf); + + ResponseAppend_P(PSTR("}")); } static void run_pid() { + #define PID_BACKWARD_COMPATIBLE + // This part is left inside to regularly publish the PID Power via + // `%topic%/PID {"power":"0.000"}` +#ifdef PID_BACKWARD_COMPATIBLE double power = pid.tick(pid_current_time_secs); - char buf[10]; - dtostrfd(power, 3, buf); - snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", buf); + char str_buf[FLOATSZ]; + dtostrfd(power, 3, str_buf); + snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", str_buf); MqttPublishPrefixTopic_P(TELE, "PID", false); - AddLog_P (LOG_LEVEL_INFO, PSTR("PID: power: %s"), buf); +#endif // PID_BACKWARD_COMPATIBLE #if defined PID_SHUTTER // send output as a position from 0-100 to defined shutter @@ -363,7 +411,10 @@ bool Xdrv92(byte function) #endif // PID_USE_LOCAL_SENSOR break; case FUNC_COMMAND: - result = PID_Command(); + result = DecodeCommand(kPIDCommands, PIDCommand); + break; + case FUNC_JSON_APPEND: + PIDShowValues(); break; } return result; From ea24f1b3631f9c61f4266cda6248c1a888e7a06d Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:04:02 +0100 Subject: [PATCH 13/28] remove logging --- tasmota/xdrv_91_timeprop.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_91_timeprop.ino b/tasmota/xdrv_91_timeprop.ino index ad554c422..b65faa4d7 100644 --- a/tasmota/xdrv_91_timeprop.ino +++ b/tasmota/xdrv_91_timeprop.ino @@ -106,7 +106,7 @@ void Timeprop_Set_Power( int index, float power ) void Timeprop_Init() { - AddLog_P(LOG_LEVEL_INFO, PSTR("TPR: Timeprop Init")); + // AddLog_P(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}; From 879104ef27d3ce9f470f85c2a12ca57350bb8404 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:04:25 +0100 Subject: [PATCH 14/28] rename xdrv_92_pid.ino -> xdrv_48_pid.ino --- tasmota/{xdrv_92_pid.ino => xdrv_48_pid.ino} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tasmota/{xdrv_92_pid.ino => xdrv_48_pid.ino} (99%) diff --git a/tasmota/xdrv_92_pid.ino b/tasmota/xdrv_48_pid.ino similarity index 99% rename from tasmota/xdrv_92_pid.ino rename to tasmota/xdrv_48_pid.ino index a887ae8f8..02a21067e 100644 --- a/tasmota/xdrv_92_pid.ino +++ b/tasmota/xdrv_48_pid.ino @@ -390,7 +390,7 @@ static void run_pid() * Interface \*********************************************************************************************/ -#define XDRV_92 92 +#define XDRV_48 48 bool Xdrv92(byte function) //bool XDRV_92(byte function) @@ -420,4 +420,4 @@ bool Xdrv92(byte function) return result; } -#endif // USE_TIMEPROP +#endif // USE_PID From d6e369bead9865ab41296a3f3cce049974c68d6f Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:05:16 +0100 Subject: [PATCH 15/28] rename xdrv_48_pid.ino -> xdrv_49_pid.ino --- tasmota/{xdrv_48_pid.ino => xdrv_49_pid.ino} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tasmota/{xdrv_48_pid.ino => xdrv_49_pid.ino} (99%) diff --git a/tasmota/xdrv_48_pid.ino b/tasmota/xdrv_49_pid.ino similarity index 99% rename from tasmota/xdrv_48_pid.ino rename to tasmota/xdrv_49_pid.ino index 02a21067e..bc16c9f94 100644 --- a/tasmota/xdrv_48_pid.ino +++ b/tasmota/xdrv_49_pid.ino @@ -390,7 +390,7 @@ static void run_pid() * Interface \*********************************************************************************************/ -#define XDRV_48 48 +#define XDRV_49 49 bool Xdrv92(byte function) //bool XDRV_92(byte function) From 6ba2fdab65ba880159e86f611e679c5c3504288a Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:05:54 +0100 Subject: [PATCH 16/28] rename xdrv_91_timeprop.ino -> xdrv_48_timeprop.ino --- tasmota/{xdrv_91_timeprop.ino => xdrv_48_timeprop.ino} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tasmota/{xdrv_91_timeprop.ino => xdrv_48_timeprop.ino} (100%) diff --git a/tasmota/xdrv_91_timeprop.ino b/tasmota/xdrv_48_timeprop.ino similarity index 100% rename from tasmota/xdrv_91_timeprop.ino rename to tasmota/xdrv_48_timeprop.ino From c396a9bbdb8837227b647e4184161ccb36c2854e Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:09:23 +0100 Subject: [PATCH 17/28] conclude renaming --- tasmota/my_user_config.h | 4 ++-- tasmota/xdrv_48_timeprop.ino | 7 +++---- tasmota/xdrv_49_pid.ino | 9 ++++----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index f357a7ad0..64d6c665d 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -813,9 +813,9 @@ // -- PID and Timeprop ------------------------------ // #define use TIMEPROP // Add support for the timeprop feature (+9k2 code) - // For details on the configuration please see the header of tasmota/xdrv_91_timeprop.ino + // For details on the configuration please see the header of tasmota/xdrv_48_timeprop.ino // #define USE_PID // Add suport for the PID feature (+11k5 code) - // For details on the configuration please see the header of tasmota/xdrv_92_pid.ino + // For details on the configuration please see the header of tasmota/xdrv_49_pid.ino // -- End of general directives ------------------- /*********************************************************************************************\ diff --git a/tasmota/xdrv_48_timeprop.ino b/tasmota/xdrv_48_timeprop.ino index b65faa4d7..a457c8d94 100644 --- a/tasmota/xdrv_48_timeprop.ino +++ b/tasmota/xdrv_48_timeprop.ino @@ -1,5 +1,5 @@ /* - xdrv_91_timeprop.ino - Timeprop support for Sonoff-Tasmota + xdrv_48_timeprop.ino - Timeprop support for Sonoff-Tasmota Copyright (C) 2018 Colin Law and Thomas Herrmann This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -197,10 +197,9 @@ bool Timeprop_Command() * Interface \*********************************************************************************************/ -#define XDRV_91 91 +#define XDRV_48 48 -bool Xdrv91(byte function) -//bool XDRV_91(byte function) +bool Xdrv48(byte function) { bool result = false; diff --git a/tasmota/xdrv_49_pid.ino b/tasmota/xdrv_49_pid.ino index bc16c9f94..eaf2ea92f 100644 --- a/tasmota/xdrv_49_pid.ino +++ b/tasmota/xdrv_49_pid.ino @@ -1,5 +1,5 @@ /* - xdrv_92_pid.ino - PID algorithm plugin for Sonoff-Tasmota + xdrv_49_pid.ino - PID algorithm plugin for Sonoff-Tasmota Copyright (C) 2018 Colin Law and Thomas Herrmann This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,7 @@ * Clone the library https://github.com/colinl/process-control.git from Github * into a subfolder of lib. * If you want to use a time proportioned relay output with this then also get - * xdrv_91_timeprop.ino + * xdrv_49_timeprop.ino * In user_config.h or user_config_override.h include code as follows: #define USE_PID // include the pid feature (+4.3k) @@ -105,7 +105,7 @@ // process, set this to indicate which timeprop output to use. For a device // with just one relay then this will be 1. // It is then also necessary to define USE_TIMEPROP and set the output up as - // explained in xdrv_91_timeprop.ino + // explained in xdrv_49_timeprop.ino // To disable this feature leave this undefined (undefined, not defined to nothing). #define PID_USE_LOCAL_SENSOR // If defined then the local sensor will be used for pv. Leave undefined if @@ -392,8 +392,7 @@ static void run_pid() #define XDRV_49 49 -bool Xdrv92(byte function) -//bool XDRV_92(byte function) +bool Xdrv49(byte function) { bool result = false; From ce1a10439733c29369a16748c18d8ff353d45b07 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:15:43 +0100 Subject: [PATCH 18/28] turn the /PID reporting off by default --- tasmota/xdrv_49_pid.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tasmota/xdrv_49_pid.ino b/tasmota/xdrv_49_pid.ino index eaf2ea92f..676602003 100644 --- a/tasmota/xdrv_49_pid.ino +++ b/tasmota/xdrv_49_pid.ino @@ -363,10 +363,9 @@ void PIDShowValues(void) { static void run_pid() { - #define PID_BACKWARD_COMPATIBLE +#ifdef PID_BACKWARD_COMPATIBLE // This part is left inside to regularly publish the PID Power via // `%topic%/PID {"power":"0.000"}` -#ifdef PID_BACKWARD_COMPATIBLE double power = pid.tick(pid_current_time_secs); char str_buf[FLOATSZ]; dtostrfd(power, 3, str_buf); From 8b5961c37d1a4ec604f174643dfafe3e6574d0a0 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:53:46 +0100 Subject: [PATCH 19/28] remove code that did not seem to be necessary --- tasmota/xdrv_48_timeprop.ino | 51 ------------------------------------ 1 file changed, 51 deletions(-) diff --git a/tasmota/xdrv_48_timeprop.ino b/tasmota/xdrv_48_timeprop.ino index a457c8d94..6e8537dbb 100644 --- a/tasmota/xdrv_48_timeprop.ino +++ b/tasmota/xdrv_48_timeprop.ino @@ -82,11 +82,6 @@ # include "Timeprop.h" -#define D_CMND_TIMEPROP "timeprop_" -#define D_CMND_TIMEPROP_SETPOWER "setpower_" // add index no on end (0:8) and data is power 0:1 - -enum TimepropCommands { CMND_TIMEPROP_SETPOWER }; -const char kTimepropCommands[] PROGMEM = D_CMND_TIMEPROP_SETPOWER; static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; static int relayNos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; @@ -148,50 +143,7 @@ void Timeprop_Xdrv_Power() { /* } XdrvMailbox; */ // To get here post with topic cmnd/timeprop_setpower_n where n is index into timeprops 0:7 -bool Timeprop_Command() -{ - char command [CMDSZ]; - bool serviced = true; - uint8_t ua_prefix_len = strlen(D_CMND_TIMEPROP); // to detect prefix of command - /* - snprintf_P(log_data, sizeof(log_data), "Command called: " - "index: %d data_len: %d payload: %d topic: %s data: %s\n", - XdrvMailbox.index, - XdrvMailbox.data_len, - XdrvMailbox.payload, - (XdrvMailbox.payload >= 0 ? XdrvMailbox.topic : ""), - (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); - AddLog(LOG_LEVEL_INFO); - */ - 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) { - /* - snprintf_P(log_data, sizeof(log_data), "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 : "")); - AddLog(LOG_LEVEL_INFO); - */ - if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS) { - timeprops[XdrvMailbox.index].setPower( atof(XdrvMailbox.data), timeprop_current_time_secs ); - } - snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"" D_CMND_TIMEPROP D_CMND_TIMEPROP_SETPOWER "%d\":\"%s\"}"), - XdrvMailbox.index, XdrvMailbox.data); - } - else { - serviced = false; - } - } else { - serviced = false; - } - return serviced; -} /*********************************************************************************************\ * Interface @@ -210,9 +162,6 @@ bool Xdrv48(byte function) case FUNC_EVERY_SECOND: Timeprop_Every_Second(); break; - case FUNC_COMMAND: - result = Timeprop_Command(); - break; case FUNC_SET_POWER: Timeprop_Xdrv_Power(); break; From c820f435bef95be0d9fe651cc9c92424e87e5ed5 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:54:09 +0100 Subject: [PATCH 20/28] update config options --- tasmota/xdrv_49_pid.ino | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tasmota/xdrv_49_pid.ino b/tasmota/xdrv_49_pid.ino index 676602003..c25359ad6 100644 --- a/tasmota/xdrv_49_pid.ino +++ b/tasmota/xdrv_49_pid.ino @@ -116,9 +116,7 @@ #define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the // number of the shutter here. Otherwise leave this commented out - #define PID_DEBUGGING // Increase number of log messages - - #define PID_REPORT_SETTINGS // If defined, the SENSOR output will provide more extensive json + #define PID_REPORT_MORE_SETTINGS // If defined, the SENSOR output will provide more extensive json // output in the PID section // #define PID_BACKWARD_COMPATIBLE // Preserve the backward compatible reporting of PID power via @@ -363,10 +361,10 @@ void PIDShowValues(void) { static void run_pid() { + double power = pid.tick(pid_current_time_secs); #ifdef PID_BACKWARD_COMPATIBLE // This part is left inside to regularly publish the PID Power via // `%topic%/PID {"power":"0.000"}` - double power = pid.tick(pid_current_time_secs); char str_buf[FLOATSZ]; dtostrfd(power, 3, str_buf); snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", str_buf); From 26372c3c11941b7902d851ba49e637b5095ee3d5 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 7 Jan 2021 00:01:21 +0100 Subject: [PATCH 21/28] adjust code size changes --- tasmota/my_user_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 64d6c665d..5040f6c09 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -812,9 +812,9 @@ //#define USE_PROMETHEUS // Add support for https://prometheus.io/ metrics exporting over HTTP /metrics endpoint // -- PID and Timeprop ------------------------------ -// #define use TIMEPROP // Add support for the timeprop feature (+9k2 code) +// #define use TIMEPROP // Add support for the timeprop feature (+0k8 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 (+11k5 code) +// #define USE_PID // Add suport for the PID feature (+11k1 code) // For details on the configuration please see the header of tasmota/xdrv_49_pid.ino // -- End of general directives ------------------- From f190854e6b1e2ee7979c0c5f4810841d67cc1452 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 7 Jan 2021 09:41:42 +0100 Subject: [PATCH 22/28] one byte change to rerun tests --- tasmota/xdrv_49_pid.ino | 1 - 1 file changed, 1 deletion(-) diff --git a/tasmota/xdrv_49_pid.ino b/tasmota/xdrv_49_pid.ino index c25359ad6..37b8774a3 100644 --- a/tasmota/xdrv_49_pid.ino +++ b/tasmota/xdrv_49_pid.ino @@ -415,5 +415,4 @@ bool Xdrv49(byte function) } return result; } - #endif // USE_PID From 1817bec751f72df330a564f54fd06d5737d8363b Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Thu, 7 Jan 2021 10:57:24 +0100 Subject: [PATCH 23/28] filemanager switch ufs ffs --- tasmota/xdrv_10_scripter.ino | 2 +- tasmota/xdrv_50_filesystem.ino | 84 ++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 7992ab555..261904bd5 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -2244,7 +2244,7 @@ chknext: if (!strncmp(vname, "fsi(", 4)) { lp = GetNumericArgument(lp + 4, OPER_EQU, &fvar, 0); - fvar = ufs_fsinfo(fvar); + fvar = ufs_fsinfo(fvar, 0); lp++; len = 0; goto exit; diff --git a/tasmota/xdrv_50_filesystem.ino b/tasmota/xdrv_50_filesystem.ino index 488f0b948..ca1f15fec 100644 --- a/tasmota/xdrv_50_filesystem.ino +++ b/tasmota/xdrv_50_filesystem.ino @@ -48,6 +48,8 @@ The driver enabled by #define USE_UFILESYS #define SDCARD_CS_PIN 4 #endif +#define FFS_2 + #ifdef ESP8266 #include #include @@ -73,12 +75,16 @@ The driver enabled by #define USE_UFILESYS FS *ufsp; // flash file system pointer on esp32 FS *ffsp; +// local pointer for file managment +FS *dfsp; + char ufs_path[48]; File ufs_upload_file; +uint8_t ufs_dir; // 0 = none, 1 = SD, 2 = ffat, 3 = littlefs -// spiffs should be obsolete uint8_t ufs_type; +uint8_t ffs_type; #define UFS_TNONE 0 #define UFS_TSDC 1 #define UFS_TFAT 2 @@ -87,14 +93,15 @@ uint8_t ufs_type; void UfsInit(void) { ufs_type = 0; ffsp = 0; + ufs_dir = 0; // check for fs options, // 1. check for SD card // 2. check for littlefs or FAT #ifdef USE_SDCARD - if (TasmotaGlobal.spi_enabled) { -// if (1) { +// if (TasmotaGlobal.spi_enabled) { + if (1) { int8_t cs = SDCARD_CS_PIN; if (PinUsed(GPIO_SDCARD_CS)) { cs = Pin(GPIO_SDCARD_CS); @@ -108,6 +115,8 @@ void UfsInit(void) { ufsp = &SD; #endif // ESP32 ufs_type = UFS_TSDC; + dfsp = ufsp; +#ifdef FFS_2 // now detect ffs ffsp = &LITTLEFS; if (!LITTLEFS.begin()) { @@ -117,7 +126,13 @@ void UfsInit(void) { ffsp = 0; return; } + ffs_type = UFS_TFAT; + ufs_dir = 1; + return; } + ffs_type = UFS_TLFS; + ufs_dir = 1; +#endif // FFS_2 return; } } @@ -146,6 +161,7 @@ void UfsInit(void) { #endif // ESP32 ufs_type = UFS_TLFS; ffsp = ufsp; + dfsp = ufsp; return; } @@ -204,18 +220,24 @@ bool TfsLoadFile(const char *fname, uint8_t *buf, uint32_t len) { return true; } -uint32_t ufs_fsinfo(uint32_t sel) { +uint32_t ufs_fsinfo(uint32_t sel, uint32_t type) { uint32_t result = 0; + FS *ifsp = ufsp; + uint8_t itype = ufs_type; + if (type) { + ifsp = ffsp; + itype = ffs_type; + } #ifdef ESP8266 FSInfo64 fsinfo; #endif // ESP8266 - switch (ufs_type) { + switch (itype) { case UFS_TSDC: #ifdef USE_SDCARD #ifdef ESP8266 - ufsp->info64(fsinfo); + ifsp->info64(fsinfo); if (sel == 0) { result = fsinfo.totalBytes; } else { @@ -234,7 +256,7 @@ uint32_t ufs_fsinfo(uint32_t sel) { case UFS_TLFS: #ifdef ESP8266 - ufsp->info64(fsinfo); + ifsp->info64(fsinfo); if (sel == 0) { result = fsinfo.totalBytes; } else { @@ -323,17 +345,17 @@ void (* const kUFSCommand[])(void) PROGMEM = { &UFS_info, &UFS_type, &UFS_size, &UFS_free}; void UFS_info(void) { - Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}}"), ufs_type, ufs_fsinfo(0), ufs_fsinfo(1)); + Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}}"), ufs_type, ufs_fsinfo(0, 0), ufs_fsinfo(1, 0)); } void UFS_type(void) { ResponseCmndNumber(ufs_type); } void UFS_size(void) { - ResponseCmndNumber(ufs_fsinfo(0)); + ResponseCmndNumber(ufs_fsinfo(0, 0)); } void UFS_free(void) { - ResponseCmndNumber(ufs_fsinfo(1)); + ResponseCmndNumber(ufs_fsinfo(1, 0)); } const char UFS_WEB_DIR[] PROGMEM = @@ -343,7 +365,15 @@ const char UFS_FORM_FILE_UPLOAD[] PROGMEM = "
" "
 " D_MANAGE_FILE_SYSTEM " "; const char UFS_FORM_FILE_UPGc[] PROGMEM = - "
" D_FS_SIZE " %s kB - " D_FS_FREE " %s kB
"; + "
" D_FS_SIZE " %s kB - " D_FS_FREE " %s kB"; + +const char UFS_FORM_FILE_UPGc1[] PROGMEM = + "   %s"; + +const char UFS_FORM_FILE_UPGc2[] PROGMEM = + "
"; + + const char UFS_FORM_FILE_UPG[] PROGMEM = "
" "

" @@ -379,16 +409,34 @@ void UFSdirectory(void) { } } + if (Webserver->hasArg("dir")) { + String stmp = Webserver->arg("dir"); + ufs_dir = atoi(stmp.c_str()); + if (ufs_dir == 1) { + dfsp = ufsp; + } else { + if (ffsp) { + dfsp = ffsp; + } + } + } + WSContentStart_P(PSTR(D_MANAGE_FILE_SYSTEM)); WSContentSendStyle(); WSContentSend_P(UFS_FORM_FILE_UPLOAD); char ts[16]; char fs[16]; - UFS_form1000(ufs_fsinfo(0), ts, '.'); - UFS_form1000(ufs_fsinfo(1), fs, '.'); + UFS_form1000(ufs_fsinfo(0, ufs_dir == 1 ? 0:1), ts, '.'); + UFS_form1000(ufs_fsinfo(1, ufs_dir == 1 ? 0:1), fs, '.'); + WSContentSend_P(UFS_FORM_FILE_UPGc, WebColor(COL_TEXT), ts, fs); + if (ufs_dir) { + WSContentSend_P(UFS_FORM_FILE_UPGc1, WiFi.localIP().toString().c_str(),ufs_dir == 1 ? 2:1, ufs_dir == 1 ? "UFS":"FFS"); + } + WSContentSend_P(UFS_FORM_FILE_UPGc2); + WSContentSend_P(UFS_FORM_FILE_UPG, D_SCRIPT_UPLOAD); WSContentSend_P(UFS_FORM_SDC_DIRa); @@ -408,7 +456,7 @@ void UFS_ListDir(char *path, uint8_t depth) { char format[12]; sprintf(format, "%%-%ds", 24 - depth); - File dir = ufsp->open(path, UFS_FILE_READ); + File dir = dfsp->open(path, UFS_FILE_READ); if (dir) { dir.rewindDirectory(); if (strlen(path)>1) { @@ -478,12 +526,12 @@ uint8_t UFS_DownloadFile(char *file) { File download_file; WiFiClient download_Client; - if (!ufsp->exists(file)) { + if (!dfsp->exists(file)) { AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: File not found")); return 0; } - download_file = ufsp->open(file, UFS_FILE_READ); + download_file = dfsp->open(file, UFS_FILE_READ); if (!download_file) { AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: Could not open file")); return 0; @@ -540,8 +588,8 @@ void UFS_Upload(void) { if (upload.status == UPLOAD_FILE_START) { char npath[48]; sprintf(npath, "%s/%s", ufs_path, upload.filename.c_str()); - ufsp->remove(npath); - ufs_upload_file = ufsp->open(npath, UFS_FILE_WRITE); + dfsp->remove(npath); + ufs_upload_file = dfsp->open(npath, UFS_FILE_WRITE); if (!ufs_upload_file) { Web.upload_error = 1; } } else if (upload.status == UPLOAD_FILE_WRITE) { From 3930bc97d71be623df96720ff0e735ea9c5815cc Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Thu, 7 Jan 2021 11:01:04 +0100 Subject: [PATCH 24/28] Update xdrv_50_filesystem.ino --- tasmota/xdrv_50_filesystem.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasmota/xdrv_50_filesystem.ino b/tasmota/xdrv_50_filesystem.ino index ca1f15fec..e12f6374a 100644 --- a/tasmota/xdrv_50_filesystem.ino +++ b/tasmota/xdrv_50_filesystem.ino @@ -100,8 +100,8 @@ void UfsInit(void) { #ifdef USE_SDCARD -// if (TasmotaGlobal.spi_enabled) { - if (1) { + if (TasmotaGlobal.spi_enabled) { +// if (1) { int8_t cs = SDCARD_CS_PIN; if (PinUsed(GPIO_SDCARD_CS)) { cs = Pin(GPIO_SDCARD_CS); From 8c9c4d4d66ad73d7882258d15b1c12663fa62bb4 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Thu, 7 Jan 2021 11:03:02 +0100 Subject: [PATCH 25/28] Update xdrv_50_filesystem.ino --- tasmota/xdrv_50_filesystem.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/tasmota/xdrv_50_filesystem.ino b/tasmota/xdrv_50_filesystem.ino index e12f6374a..40ece33c9 100644 --- a/tasmota/xdrv_50_filesystem.ino +++ b/tasmota/xdrv_50_filesystem.ino @@ -156,6 +156,7 @@ void UfsInit(void) { } ufs_type = UFS_TFAT; ffsp = ufsp; + dfsp = ufsp; return; } #endif // ESP32 From 5321cf9866008f95b634d1dbbb552aeff6e0bb88 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Thu, 7 Jan 2021 12:37:10 +0100 Subject: [PATCH 26/28] Update Italian language --- tasmota/language/it_IT.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index ecc52606f..6d8842e8e 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -1,7 +1,7 @@ /* it-IT.h - localization for Italian - Italy for Tasmota - Copyright (C) 2021 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 24.12.2020 + Copyright (C) 2021 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 07.01.2021 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -779,7 +779,7 @@ #define D_SENSOR_ST7789_DC "ST7789 - DC" #define D_SENSOR_SSD1331_CS "SSD1331 - CS" #define D_SENSOR_SSD1331_DC "SSD1331 - DC" -#define D_SENSOR_SDCARD_CS "SDCard - CS" +#define D_SENSOR_SDCARD_CS "Scheda SD - CS" // Units #define D_UNIT_AMPERE "A" @@ -875,9 +875,9 @@ #define D_SCRIPT_UPLOAD_FILES "Carica file" //xdrv_50_filesystem.ino -#define D_MANAGE_FILE_SYSTEM "Manage File system" -#define D_FS_SIZE "Size" -#define D_FS_FREE "Free" +#define D_MANAGE_FILE_SYSTEM "Gestione File system" +#define D_FS_SIZE "Dimensione" +#define D_FS_FREE "Liberi" //xsns_67_as3935.ino #define D_AS3935_GAIN "guadagno:" From 23cb8ac559e63e621b8be4667b80657cd3ad3769 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 7 Jan 2021 15:07:14 +0100 Subject: [PATCH 27/28] Support for time proportioned relays Support for time proportioned (``#define USE_TIMEPROP``) and optional PID (``#define USE_PID``) relay control (#10412) --- CHANGELOG.md | 3 + RELEASENOTES.md | 1 + tasmota/my_user_config.h | 11 +- tasmota/support_features.ino | 10 +- tasmota/tasmota_globals.h | 4 + tasmota/xdrv_48_timeprop.ino | 104 +++++++++------- tasmota/xdrv_49_pid.ino | 230 ++++++++++++++++++++--------------- 7 files changed, 210 insertions(+), 153 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ed29912..83a0ff3cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - Development ## [9.2.0.3] +### Added +- Support for time proportioned (``#define USE_TIMEPROP``) and optional PID (``#define USE_PID``) relay control (#10412) + ### Breaking Changed - ESP32 switch from default SPIFFS to default LittleFS file system loosing current (zigbee) files diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 51bf7b199..17f7a24b1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -75,6 +75,7 @@ The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota - Support for IR inverted leds using ``#define IR_SEND_INVERTED true`` [#10301](https://github.com/arendst/Tasmota/issues/10301) - Support for disabling 38kHz IR modulation using ``#define IR_SEND_USE_MODULATION false`` [#10301](https://github.com/arendst/Tasmota/issues/10301) - Support for SPI display driver for ST7789 TFT by Gerhard Mutz [#9037](https://github.com/arendst/Tasmota/issues/9037) +- Support for time proportioned (``#define USE_TIMEPROP``) and optional PID (``#define USE_PID``) relay control [#10412](https://github.com/arendst/Tasmota/issues/10412) - Basic support for ESP32 Odroid Go 16MB binary tasmota32-odroidgo.bin [#8630](https://github.com/arendst/Tasmota/issues/8630) - SPI display driver SSD1331 Color oled by Jeroen Vermeulen [#10376](https://github.com/arendst/Tasmota/issues/10376) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 579e65dbb..44415794f 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -774,6 +774,8 @@ //#define USE_HRE // Add support for Badger HR-E Water Meter (+1k4 code) //#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code) +//#define USE_PROMETHEUS // Add support for https://prometheus.io/ metrics exporting over HTTP /metrics endpoint + // -- Thermostat control ---------------------------- //#define USE_THERMOSTAT // Add support for Thermostat #define THERMOSTAT_CONTROLLER_OUTPUTS 1 // Number of outputs to be controlled independently @@ -808,15 +810,12 @@ #define THERMOSTAT_TEMP_BAND_NO_PEAK_DET 1 // Default temperature band in thenths of degrees celsius within no peak will be detected #define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful -// -- Prometheus exporter --------------------------- -//#define USE_PROMETHEUS // Add support for https://prometheus.io/ metrics exporting over HTTP /metrics endpoint - // -- PID and Timeprop ------------------------------ -// #define use TIMEPROP // Add support for the timeprop feature (+0k8 code) +//#define USE_TIMEPROP // Add support for the timeprop feature (+0k8 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 (+11k1 code) +//#define USE_PID // Add suport for the PID feature (+11k1 code) // For details on the configuration please see the header of tasmota/xdrv_49_pid.ino -// -- End of general directives ------------------- +// -- End of general directives --------------------- /*********************************************************************************************\ * ESP32 only features diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index db5cc9b17..6000b4b09 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -690,10 +690,14 @@ void ResponseAppendFeatures(void) feature7 |= 0x00100000; // xdsp_14_SSD1331.ino #endif #ifdef USE_UFILESYS - feature7 |= 0x00200000; + feature7 |= 0x00200000; // xdrv_50_filesystem.ino +#endif +#ifdef USE_TIMEPROP + feature7 |= 0x00400000; // xdrv_48_timeprop.ino +#endif +#ifdef USE_PID + feature7 |= 0x00800000; // xdrv_49_pid.ino #endif -// feature7 |= 0x00400000; -// feature7 |= 0x00800000; // feature7 |= 0x01000000; // feature7 |= 0x02000000; diff --git a/tasmota/tasmota_globals.h b/tasmota/tasmota_globals.h index 76675b55d..ea3785b44 100644 --- a/tasmota/tasmota_globals.h +++ b/tasmota/tasmota_globals.h @@ -163,6 +163,10 @@ String EthernetMacAddress(void); #endif #ifdef USE_EMULATION_HUE #define USE_UNISHOX_COMPRESSION // Add support for string compression +#endif + +#ifdef USE_PID +#define USE_TIMEPROP #endif // See https://github.com/esp8266/Arduino/pull/4889 diff --git a/tasmota/xdrv_48_timeprop.ino b/tasmota/xdrv_48_timeprop.ino index 6e8537dbb..bddf2653f 100644 --- a/tasmota/xdrv_48_timeprop.ino +++ b/tasmota/xdrv_48_timeprop.ino @@ -1,19 +1,24 @@ /* xdrv_48_timeprop.ino - Timeprop support for Sonoff-Tasmota - Copyright (C) 2018 Colin Law and Thomas Herrmann + + Copyright (C) 2021 Colin Law and Thomas Herrmann + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU General Public License along with this program. If not, see . */ -/** +#ifdef USE_TIMEPROP +/*********************************************************************************************\ * Code to drive one or more relays in a time proportioned manner give a * required power value. * @@ -74,33 +79,48 @@ #define TIMEPROP_RELAYS 1, 2 // which relay to control 1:8 * Publish values between 0 and 1 to the topic(s) described above - * -**/ +\*********************************************************************************************/ +#ifndef TIMEPROP_NUM_OUTPUTS +#define TIMEPROP_NUM_OUTPUTS 1 // how many outputs to control (with separate alogorithm for each) +#endif +#ifndef TIMEPROP_CYCLETIMES +#define TIMEPROP_CYCLETIMES 60 // cycle time seconds +#endif +#ifndef TIMEPROP_DEADTIMES +#define TIMEPROP_DEADTIMES 0 // actuator action time seconds +#endif +#ifndef TIMEPROP_OPINVERTS +#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 +#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 -#ifdef USE_TIMEPROP +#include "Timeprop.h" -# include "Timeprop.h" - - -static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; -static int relayNos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; -static long currentRelayStates = 0; // current actual relay states. Bit 0 first relay - -static long timeprop_current_time_secs = 0; // a counter that counts seconds since initialisation +struct { + Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; + int relay_nos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; + long current_relay_states = 0; // current actual relay states. Bit 0 first relay + long current_time_secs = 0; // a counter that counts seconds since initialisation +} Tprop; /* call this from elsewhere if required to set the power value for one of the timeprop instances */ /* index specifies which one, 0 up */ -void Timeprop_Set_Power( int index, float power ) -{ - if (index >= 0 && index < TIMEPROP_NUM_OUTPUTS) - { - timeprops[index].setPower( power, timeprop_current_time_secs); +void TimepropSetPower(int index, float power) { + if (index >= 0 && index < TIMEPROP_NUM_OUTPUTS) { + Tprop.timeprops[index].setPower( power, Tprop.current_time_secs); } } -void Timeprop_Init() -{ +void TimepropInit(void) { // AddLog_P(LOG_LEVEL_INFO, PSTR("TPR: Timeprop Init")); int cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES}; int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES}; @@ -108,29 +128,29 @@ void Timeprop_Init() int fallbacks[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_FALLBACK_POWERS}; int maxIntervals[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_MAX_UPDATE_INTERVALS}; - for (int i=0; i. */ -/** - * Code to - * - * Usage: - * Place this file in the sonoff folder. - * Clone the library https://github.com/colinl/process-control.git from Github - * into a subfolder of lib. - * If you want to use a time proportioned relay output with this then also get - * xdrv_49_timeprop.ino - * In user_config.h or user_config_override.h include code as follows: +#ifdef USE_PID +/*********************************************************************************************\ + * Uses the library https://github.com/colinl/process-control.git from Github + * In user_config_override.h include code as follows: #define USE_PID // include the pid feature (+4.3k) #define PID_SETPOINT 19.5 // Setpoint value. This is the process value that the process is @@ -113,7 +111,7 @@ // If not using the sensor then you can supply process values via MQTT using // cmnd PidPv - #define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the + #define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the // number of the shutter here. Otherwise leave this commented out #define PID_REPORT_MORE_SETTINGS // If defined, the SENSOR output will provide more extensive json @@ -127,13 +125,44 @@ * Help with using the PID algorithm and with loop tuning can be found at * http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html * This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on - * the code there and the tuning techique described there should work just the same. + * the code there and the tuning technique described there should work just the same. +\*********************************************************************************************/ - * -**/ +#ifndef PID_SETPOINT +#define PID_SETPOINT 19.5 // [PidSp] Setpoint value. +#endif +#ifndef PID_PROPBAND +#define PID_PROPBAND 5 // [PidPb] Proportional band in process units (eg degrees). +#endif +#ifndef PID_INTEGRAL_TIME +#define PID_INTEGRAL_TIME 1800 // [PidTi] Integral time seconds. +#endif +#ifndef PID_DERIVATIVE_TIME +#define PID_DERIVATIVE_TIME 15 // [PidTd] Derivative time seconds. +#endif +#ifndef PID_INITIAL_INT +#define PID_INITIAL_INT 0.5 // Initial integral value (0:1). +#endif +#ifndef PID_MAX_INTERVAL +#define PID_MAX_INTERVAL 300 // [PidMaxInterval] This is the maximum time in seconds between samples. +#endif +#ifndef PID_DERIV_SMOOTH_FACTOR +#define PID_DERIV_SMOOTH_FACTOR 3 // [PidDSmooth] +#endif +#ifndef PID_AUTO +#define PID_AUTO 1 // [PidAuto] Auto mode 1 or 0 (for manual). +#endif +#ifndef PID_MANUAL_POWER +#define PID_MANUAL_POWER 0 // [PidManualPower] Power output when in manual mode or fallback mode. +#endif +#ifndef PID_UPDATE_SECS +#define PID_UPDATE_SECS 0 // [PidUpdateSecs] How often to run the pid algorithm +#endif - -#ifdef USE_PID +#define PID_USE_TIMPROP 1 // To disable this feature leave this undefined +//#define PID_USE_LOCAL_SENSOR // [PidPv] If defined then the local sensor will be used for pv. +//#define PID_SHUTTER 1 // Number of the shutter here. Otherwise leave this commented out +#define PID_REPORT_MORE_SETTINGS // If defined, the SENSOR output will provide more extensive json #include "PID.h" @@ -166,11 +195,11 @@ const char kPIDCommands[] PROGMEM = D_PRFX_PID "|" // Prefix ; void (* const PIDCommand[])(void) PROGMEM = { - &CmndSetPv, + &CmndSetPv, &CmndSetSp, &CmndSetPb, &CmndSetTi, - &cmndsetTd, + &CmndSetTd, &CmndSetInitialInt, &CmndSetDSmooth, &CmndSetAuto, @@ -179,32 +208,33 @@ void (* const PIDCommand[])(void) PROGMEM = { &CmndSetUpdateSecs }; -static PID pid; -static int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run -static int max_interval = PID_MAX_INTERVAL; -static unsigned long last_pv_update_secs = 0; -static bool run_pid_now = false; // tells PID_Every_Second to run the pid algorithm +struct { + PID pid; + int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run + int max_interval = PID_MAX_INTERVAL; + unsigned long last_pv_update_secs = 0; + bool run_pid_now = false; // tells PID_Every_Second to run the pid algorithm + long current_time_secs = 0; // a counter that counts seconds since initialisation +} Pid; -static long pid_current_time_secs = 0; // a counter that counts seconds since initialisation - -void PID_Init() +void PIDInit() { - pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, + Pid.pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); } -void PID_Every_Second() { +void PIDEverySecond() { static int sec_counter = 0; - pid_current_time_secs++; // increment time - // run the pid algorithm if run_pid_now is true or if the right number of seconds has passed or if too long has + Pid.current_time_secs++; // increment time + // run the pid algorithm if Pid.run_pid_now is true or if the right number of seconds has passed or if too long has // elapsed since last pv update. If too long has elapsed the the algorithm will deal with that. - if (run_pid_now || pid_current_time_secs - last_pv_update_secs > max_interval || (update_secs != 0 && sec_counter++ % update_secs == 0)) { - run_pid(); - run_pid_now = false; + if (Pid.run_pid_now || Pid.current_time_secs - Pid.last_pv_update_secs > Pid.max_interval || (Pid.update_secs != 0 && sec_counter++ % Pid.update_secs == 0)) { + PIDRun(); + Pid.run_pid_now = false; } } -void PID_Show_Sensor() { +void PIDShowSensor() { // Called each time new sensor data available, data in mqtt data in same format // as published in tele/SENSOR // Update period is specified in TELE_PERIOD @@ -212,13 +242,13 @@ void PID_Show_Sensor() { const float temperature = TasmotaGlobal.temperature_celsius; // pass the value to the pid alogorithm to use as current pv - last_pv_update_secs = pid_current_time_secs; - pid.setPv(temperature, last_pv_update_secs); + Pid.last_pv_update_secs = Pid.current_time_secs; + Pid.pid.setPv(temperature, Pid.last_pv_update_secs); // also trigger running the pid algorithm if we have been told to run it each pv sample - if (update_secs == 0) { + if (Pid.update_secs == 0) { // this runs it at the next second - run_pid_now = true; - } + Pid.run_pid_now = true; + } } else { AddLog_P(LOG_LEVEL_ERROR, PSTR("PID: No local temperature sensor found")); } @@ -234,69 +264,69 @@ void PID_Show_Sensor() { /* } XdrvMailbox; */ void CmndSetPv(void) { - last_pv_update_secs = pid_current_time_secs; - pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); + Pid.last_pv_update_secs = Pid.current_time_secs; + Pid.pid.setPv(atof(XdrvMailbox.data), Pid.last_pv_update_secs); // also trigger running the pid algorithm if we have been told to run it each pv sample - if (update_secs == 0) { + if (Pid.update_secs == 0) { // this runs it at the next second - run_pid_now = true; + Pid.run_pid_now = true; } } void CmndSetSp(void) { - pid.setSp(atof(XdrvMailbox.data)); + Pid.pid.setSp(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data)); } void CmndSetPb(void) { - pid.setPb(atof(XdrvMailbox.data)); + Pid.pid.setPb(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data)); } void CmndSetTi(void) { - pid.setTi(atof(XdrvMailbox.data)); + Pid.pid.setTi(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data)); } -void cmndsetTd(void) { - pid.setTd(atof(XdrvMailbox.data)); +void CmndSetTd(void) { + Pid.pid.setTd(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data)); } void CmndSetInitialInt(void) { - pid.setInitialInt(atof(XdrvMailbox.data)); + Pid.pid.setInitialInt(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data)); } void CmndSetDSmooth(void) { - pid.setDSmooth(atof(XdrvMailbox.data)); + Pid.pid.setDSmooth(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data)); } void CmndSetAuto(void) { - pid.setAuto(atoi(XdrvMailbox.data)); + Pid.pid.setAuto(atoi(XdrvMailbox.data)); ResponseCmndNumber(atoi(XdrvMailbox.data)); } void CmndSetManualPower(void) { - pid.setManualPower(atof(XdrvMailbox.data)); + Pid.pid.setManualPower(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data)); } void CmndSetMaxInterval(void) { - pid.setMaxInterval(atoi(XdrvMailbox.data)); + Pid.pid.setMaxInterval(atoi(XdrvMailbox.data)); ResponseCmndNumber(atoi(XdrvMailbox.data)); } // case CMND_PID_SETUPDATE_SECS: -// update_secs = atoi(XdrvMailbox.data) ; -// if (update_secs < 0) -// update_secs = 0; +// Pid.update_secs = atoi(XdrvMailbox.data) ; +// if (Pid.update_secs < 0) +// Pid.update_secs = 0; void CmndSetUpdateSecs(void) { - update_secs = (atoi(XdrvMailbox.data)); - if (update_secs < 0) - update_secs = 0; - ResponseCmndNumber(update_secs); + Pid.update_secs = (atoi(XdrvMailbox.data)); + if (Pid.update_secs < 0) + Pid.update_secs = 0; + ResponseCmndNumber(Pid.update_secs); } void PIDShowValues(void) { @@ -307,61 +337,60 @@ void PIDShowValues(void) { ResponseAppend_P(PSTR(",\"PID\":{")); // #define D_CMND_PID_SETPV "Pv" - d_buf = pid.getPv(); + d_buf = Pid.pid.getPv(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidPv\":%s,"), str_buf); // #define D_CMND_PID_SETSETPOINT "Sp" - d_buf = pid.getSp(); + d_buf = Pid.pid.getSp(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidSp\":%s,"), str_buf); #ifdef PID_REPORT_MORE_SETTINGS // #define D_CMND_PID_SETPROPBAND "Pb" - d_buf = pid.getPb(); + d_buf = Pid.pid.getPb(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidPb\":%s,"), str_buf); // #define D_CMND_PID_SETINTEGRAL_TIME "Ti" - d_buf = pid.getTi(); + d_buf = Pid.pid.getTi(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidTi\":%s,"), str_buf); // #define D_CMND_PID_SETDERIVATIVE_TIME "Td" - d_buf = pid.getTd(); + d_buf = Pid.pid.getTd(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidTd\":%s,"), str_buf); // #define D_CMND_PID_SETINITIAL_INT "Initint" - d_buf = pid.getInitialInt(); + d_buf = Pid.pid.getInitialInt(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidInitialInt\":%s,"), str_buf); // #define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "DSmooth" - d_buf = pid.getDSmooth(); + d_buf = Pid.pid.getDSmooth(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidDSmooth\":%s,"), str_buf); // #define D_CMND_PID_SETAUTO "Auto" - chr_buf = pid.getAuto(); + chr_buf = Pid.pid.getAuto(); ResponseAppend_P(PSTR("\"PidAuto\":%d,"), chr_buf); // #define D_CMND_PID_SETMANUAL_POWER "ManualPower" - d_buf = pid.getManualPower(); + d_buf = Pid.pid.getManualPower(); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidManualPower\":%s,"), str_buf); // #define D_CMND_PID_SETMAX_INTERVAL "MaxInterval" - i_buf = pid.getMaxInterval(); + i_buf = Pid.pid.getMaxInterval(); ResponseAppend_P(PSTR("\"PidMaxInterval\":%d,"), i_buf); // #define D_CMND_PID_SETUPDATE_SECS "UpdateSecs" - ResponseAppend_P(PSTR("\"PidUpdateSecs\":%d,"), update_secs); + ResponseAppend_P(PSTR("\"PidUpdateSecs\":%d,"), Pid.update_secs); #endif // PID_REPORT_MORE_SETTINGS // The actual power value - d_buf = pid.tick(pid_current_time_secs); + d_buf = Pid.pid.tick(Pid.current_time_secs); dtostrfd(d_buf, 2, str_buf); ResponseAppend_P(PSTR("\"PidPower\":%s"), str_buf); ResponseAppend_P(PSTR("}")); } -static void run_pid() -{ - double power = pid.tick(pid_current_time_secs); +void PIDRun(void) { + double power = Pid.pid.tick(Pid.current_time_secs); #ifdef PID_BACKWARD_COMPATIBLE // This part is left inside to regularly publish the PID Power via // `%topic%/PID {"power":"0.000"}` @@ -372,14 +401,14 @@ static void run_pid() #endif // PID_BACKWARD_COMPATIBLE #if defined PID_SHUTTER - // send output as a position from 0-100 to defined shutter - int pos = power * 100; - ShutterSetPosition(PID_SHUTTER, pos); + // send output as a position from 0-100 to defined shutter + int pos = power * 100; + ShutterSetPosition(PID_SHUTTER, pos); #endif //PID_SHUTTER #if defined PID_USE_TIMPROP - // send power to appropriate timeprop output - Timeprop_Set_Power( PID_USE_TIMPROP-1, power ); + // send power to appropriate timeprop output + TimepropSetPower( PID_USE_TIMPROP-1, power ); #endif // PID_USE_TIMPROP } @@ -389,29 +418,28 @@ static void run_pid() #define XDRV_49 49 -bool Xdrv49(byte function) -{ +bool Xdrv49(byte function) { bool result = false; switch (function) { - case FUNC_INIT: - PID_Init(); - break; - case FUNC_EVERY_SECOND: - PID_Every_Second(); - break; - case FUNC_SHOW_SENSOR: - // only use this if the pid loop is to use the local sensor for pv - #if defined PID_USE_LOCAL_SENSOR - PID_Show_Sensor(); - #endif // PID_USE_LOCAL_SENSOR - break; - case FUNC_COMMAND: - result = DecodeCommand(kPIDCommands, PIDCommand); - break; - case FUNC_JSON_APPEND: - PIDShowValues(); - break; + case FUNC_INIT: + PIDInit(); + break; + case FUNC_EVERY_SECOND: + PIDEverySecond(); + break; + case FUNC_SHOW_SENSOR: + // only use this if the pid loop is to use the local sensor for pv + #if defined PID_USE_LOCAL_SENSOR + PIDShowSensor(); + #endif // PID_USE_LOCAL_SENSOR + break; + case FUNC_COMMAND: + result = DecodeCommand(kPIDCommands, PIDCommand); + break; + case FUNC_JSON_APPEND: + PIDShowValues(); + break; } return result; } From 98b529ce7b3e9be07d997a0a3f968a2b16a054ac Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 7 Jan 2021 15:12:07 +0100 Subject: [PATCH 28/28] Update decode-status.py --- tools/decode-status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/decode-status.py b/tools/decode-status.py index 85ada62c6..5241b850d 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -243,7 +243,7 @@ a_features = [[ "USE_EZODO","USE_EZORGB","USE_EZOPMP","USE_AS608", "USE_SHELLY_DIMMER","USE_RC522","USE_FTC532","USE_DISPLAY_EPAPER_42", "USE_DISPLAY_ILI9488","USE_DISPLAY_SSD1351","USE_DISPLAY_RA8876","USE_DISPLAY_ST7789", - "USE_DISPLAY_SSD1331","USE_UFILESYS","","", + "USE_DISPLAY_SSD1331","USE_UFILESYS","USE_TIMEPROP","USE_PID", "","","","", "","","","" ]]