From 8eafc7d474b632b1c137f72f2ba2752c20329533 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 4 Jan 2021 10:29:12 +0100 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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 89c3c44754c166fb156a416d2b89bfe5455782b7 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 21:19:39 +0100 Subject: [PATCH 09/20] 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 dab488229f30a8ad28ffdf68b82353c50c195734 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 6 Jan 2021 23:00:33 +0100 Subject: [PATCH 10/20] 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 11/20] 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] 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 17/20] 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 18/20] 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 19/20] 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 20/20] 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