Support for time proportioned relays

Support for time proportioned (``#define USE_TIMEPROP``) and optional PID (``#define USE_PID``) relay control (#10412)
This commit is contained in:
Theo Arends 2021-01-07 15:07:14 +01:00
parent a814ec52a9
commit 23cb8ac559
7 changed files with 210 additions and 153 deletions

View File

@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - Development ## [Unreleased] - Development
## [9.2.0.3] ## [9.2.0.3]
### Added
- Support for time proportioned (``#define USE_TIMEPROP``) and optional PID (``#define USE_PID``) relay control (#10412)
### Breaking Changed ### Breaking Changed
- ESP32 switch from default SPIFFS to default LittleFS file system loosing current (zigbee) files - ESP32 switch from default SPIFFS to default LittleFS file system loosing current (zigbee) files

View File

@ -75,6 +75,7 @@ The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota
- Support for IR inverted leds using ``#define IR_SEND_INVERTED true`` [#10301](https://github.com/arendst/Tasmota/issues/10301) - Support for IR inverted leds using ``#define IR_SEND_INVERTED true`` [#10301](https://github.com/arendst/Tasmota/issues/10301)
- Support for disabling 38kHz IR modulation using ``#define IR_SEND_USE_MODULATION false`` [#10301](https://github.com/arendst/Tasmota/issues/10301) - Support for disabling 38kHz IR modulation using ``#define IR_SEND_USE_MODULATION false`` [#10301](https://github.com/arendst/Tasmota/issues/10301)
- Support for SPI display driver for ST7789 TFT by Gerhard Mutz [#9037](https://github.com/arendst/Tasmota/issues/9037) - Support for SPI display driver for ST7789 TFT by Gerhard Mutz [#9037](https://github.com/arendst/Tasmota/issues/9037)
- Support for time proportioned (``#define USE_TIMEPROP``) and optional PID (``#define USE_PID``) relay control [#10412](https://github.com/arendst/Tasmota/issues/10412)
- Basic support for ESP32 Odroid Go 16MB binary tasmota32-odroidgo.bin [#8630](https://github.com/arendst/Tasmota/issues/8630) - Basic support for ESP32 Odroid Go 16MB binary tasmota32-odroidgo.bin [#8630](https://github.com/arendst/Tasmota/issues/8630)
- SPI display driver SSD1331 Color oled by Jeroen Vermeulen [#10376](https://github.com/arendst/Tasmota/issues/10376) - SPI display driver SSD1331 Color oled by Jeroen Vermeulen [#10376](https://github.com/arendst/Tasmota/issues/10376)

View File

@ -774,6 +774,8 @@
//#define USE_HRE // Add support for Badger HR-E Water Meter (+1k4 code) //#define USE_HRE // Add support for Badger HR-E Water Meter (+1k4 code)
//#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code) //#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code)
//#define USE_PROMETHEUS // Add support for https://prometheus.io/ metrics exporting over HTTP /metrics endpoint
// -- Thermostat control ---------------------------- // -- Thermostat control ----------------------------
//#define USE_THERMOSTAT // Add support for Thermostat //#define USE_THERMOSTAT // Add support for Thermostat
#define THERMOSTAT_CONTROLLER_OUTPUTS 1 // Number of outputs to be controlled independently #define THERMOSTAT_CONTROLLER_OUTPUTS 1 // Number of outputs to be controlled independently
@ -808,15 +810,12 @@
#define THERMOSTAT_TEMP_BAND_NO_PEAK_DET 1 // Default temperature band in thenths of degrees celsius within no peak will be detected #define THERMOSTAT_TEMP_BAND_NO_PEAK_DET 1 // Default temperature band in thenths of degrees celsius within no peak will be detected
#define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful #define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful
// -- Prometheus exporter ---------------------------
//#define USE_PROMETHEUS // Add support for https://prometheus.io/ metrics exporting over HTTP /metrics endpoint
// -- PID and Timeprop ------------------------------ // -- PID and Timeprop ------------------------------
// #define use TIMEPROP // Add support for the timeprop feature (+0k8 code) //#define USE_TIMEPROP // Add support for the timeprop feature (+0k8 code)
// For details on the configuration please see the header of tasmota/xdrv_48_timeprop.ino // For details on the configuration please see the header of tasmota/xdrv_48_timeprop.ino
// #define USE_PID // Add suport for the PID feature (+11k1 code) //#define USE_PID // Add suport for the PID feature (+11k1 code)
// For details on the configuration please see the header of tasmota/xdrv_49_pid.ino // For details on the configuration please see the header of tasmota/xdrv_49_pid.ino
// -- End of general directives ------------------- // -- End of general directives ---------------------
/*********************************************************************************************\ /*********************************************************************************************\
* ESP32 only features * ESP32 only features

View File

@ -690,10 +690,14 @@ void ResponseAppendFeatures(void)
feature7 |= 0x00100000; // xdsp_14_SSD1331.ino feature7 |= 0x00100000; // xdsp_14_SSD1331.ino
#endif #endif
#ifdef USE_UFILESYS #ifdef USE_UFILESYS
feature7 |= 0x00200000; feature7 |= 0x00200000; // xdrv_50_filesystem.ino
#endif
#ifdef USE_TIMEPROP
feature7 |= 0x00400000; // xdrv_48_timeprop.ino
#endif
#ifdef USE_PID
feature7 |= 0x00800000; // xdrv_49_pid.ino
#endif #endif
// feature7 |= 0x00400000;
// feature7 |= 0x00800000;
// feature7 |= 0x01000000; // feature7 |= 0x01000000;
// feature7 |= 0x02000000; // feature7 |= 0x02000000;

View File

@ -163,6 +163,10 @@ String EthernetMacAddress(void);
#endif #endif
#ifdef USE_EMULATION_HUE #ifdef USE_EMULATION_HUE
#define USE_UNISHOX_COMPRESSION // Add support for string compression #define USE_UNISHOX_COMPRESSION // Add support for string compression
#endif
#ifdef USE_PID
#define USE_TIMEPROP
#endif #endif
// See https://github.com/esp8266/Arduino/pull/4889 // See https://github.com/esp8266/Arduino/pull/4889

View File

@ -1,19 +1,24 @@
/* /*
xdrv_48_timeprop.ino - Timeprop support for Sonoff-Tasmota xdrv_48_timeprop.ino - Timeprop support for Sonoff-Tasmota
Copyright (C) 2018 Colin Law and Thomas Herrmann
Copyright (C) 2021 Colin Law and Thomas Herrmann
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/** #ifdef USE_TIMEPROP
/*********************************************************************************************\
* Code to drive one or more relays in a time proportioned manner give a * Code to drive one or more relays in a time proportioned manner give a
* required power value. * required power value.
* *
@ -74,33 +79,48 @@
#define TIMEPROP_RELAYS 1, 2 // which relay to control 1:8 #define TIMEPROP_RELAYS 1, 2 // which relay to control 1:8
* Publish values between 0 and 1 to the topic(s) described above * Publish values between 0 and 1 to the topic(s) described above
* \*********************************************************************************************/
**/
#ifndef TIMEPROP_NUM_OUTPUTS
#define TIMEPROP_NUM_OUTPUTS 1 // how many outputs to control (with separate alogorithm for each)
#endif
#ifndef TIMEPROP_CYCLETIMES
#define TIMEPROP_CYCLETIMES 60 // cycle time seconds
#endif
#ifndef TIMEPROP_DEADTIMES
#define TIMEPROP_DEADTIMES 0 // actuator action time seconds
#endif
#ifndef TIMEPROP_OPINVERTS
#define TIMEPROP_OPINVERTS false // whether to invert the output
#endif
#ifndef TIMEPROP_FALLBACK_POWERS
#define TIMEPROP_FALLBACK_POWERS 0 // falls back to this if too long betwen power updates
#endif
#ifndef TIMEPROP_MAX_UPDATE_INTERVALS
#define TIMEPROP_MAX_UPDATE_INTERVALS 120 // max no secs that are allowed between power updates (0 to disable)
#endif
#ifndef TIMEPROP_RELAYS
#define TIMEPROP_RELAYS 1 // which relay to control 1:8
#endif
#ifdef USE_TIMEPROP #include "Timeprop.h"
# include "Timeprop.h" struct {
Timeprop timeprops[TIMEPROP_NUM_OUTPUTS];
int relay_nos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS};
static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; long current_relay_states = 0; // current actual relay states. Bit 0 first relay
static int relayNos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; long current_time_secs = 0; // a counter that counts seconds since initialisation
static long currentRelayStates = 0; // current actual relay states. Bit 0 first relay } Tprop;
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 */ /* call this from elsewhere if required to set the power value for one of the timeprop instances */
/* index specifies which one, 0 up */ /* index specifies which one, 0 up */
void Timeprop_Set_Power( int index, float power ) void TimepropSetPower(int index, float power) {
{ if (index >= 0 && index < TIMEPROP_NUM_OUTPUTS) {
if (index >= 0 && index < TIMEPROP_NUM_OUTPUTS) Tprop.timeprops[index].setPower( power, Tprop.current_time_secs);
{
timeprops[index].setPower( power, timeprop_current_time_secs);
} }
} }
void Timeprop_Init() void TimepropInit(void) {
{
// 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 cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES};
int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES}; int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES};
@ -108,29 +128,29 @@ void Timeprop_Init()
int fallbacks[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_FALLBACK_POWERS}; int fallbacks[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_FALLBACK_POWERS};
int maxIntervals[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_MAX_UPDATE_INTERVALS}; int maxIntervals[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_MAX_UPDATE_INTERVALS};
for (int i=0; i<TIMEPROP_NUM_OUTPUTS; i++) { for (int i = 0; i < TIMEPROP_NUM_OUTPUTS; i++) {
timeprops[i].initialise(cycleTimes[i], deadTimes[i], opInverts[i], fallbacks[i], Tprop.timeprops[i].initialise(cycleTimes[i], deadTimes[i], opInverts[i], fallbacks[i],
maxIntervals[i], timeprop_current_time_secs); maxIntervals[i], Tprop.current_time_secs);
} }
} }
void Timeprop_Every_Second() { void TimepropEverySecond(void) {
timeprop_current_time_secs++; // increment time Tprop.current_time_secs++; // increment time
for (int i=0; i<TIMEPROP_NUM_OUTPUTS; i++) { for (int i=0; i<TIMEPROP_NUM_OUTPUTS; i++) {
int newState = timeprops[i].tick(timeprop_current_time_secs); int newState = Tprop.timeprops[i].tick(Tprop.current_time_secs);
if (newState != bitRead(currentRelayStates, relayNos[i]-1)){ if (newState != bitRead(Tprop.current_relay_states, Tprop.relay_nos[i]-1)){
// remove the third parameter below if using tasmota prior to v6.0.0 // remove the third parameter below if using tasmota prior to v6.0.0
ExecuteCommandPower(relayNos[i], newState,SRC_IGNORE); ExecuteCommandPower(Tprop.relay_nos[i], newState,SRC_IGNORE);
} }
} }
} }
// called by the system each time a relay state is changed // called by the system each time a relay state is changed
void Timeprop_Xdrv_Power() { void TimepropXdrvPower(void) {
// for a single relay the state is in the lsb of index, I have think that for // for a single relay the state is in the lsb of index, I have think that for
// multiple outputs then succesive bits will hold the state but have not been // multiple outputs then succesive bits will hold the state but have not been
// able to test that // able to test that
currentRelayStates = XdrvMailbox.index; Tprop.current_relay_states = XdrvMailbox.index;
} }
/* struct XDRVMAILBOX { */ /* struct XDRVMAILBOX { */
@ -142,8 +162,7 @@ void Timeprop_Xdrv_Power() {
/* char *data; */ /* char *data; */
/* } XdrvMailbox; */ /* } XdrvMailbox; */
// To get here post with topic cmnd/timeprop_setpower_n where n is index into timeprops 0:7 // To get here post with topic cmnd/timeprop_setpower_n where n is index into Tprop.timeprops 0:7
/*********************************************************************************************\ /*********************************************************************************************\
* Interface * Interface
@ -151,20 +170,19 @@ void Timeprop_Xdrv_Power() {
#define XDRV_48 48 #define XDRV_48 48
bool Xdrv48(byte function) bool Xdrv48(byte function) {
{
bool result = false; bool result = false;
switch (function) { switch (function) {
case FUNC_INIT: case FUNC_INIT:
Timeprop_Init(); TimepropInit();
break; break;
case FUNC_EVERY_SECOND: case FUNC_EVERY_SECOND:
Timeprop_Every_Second(); TimepropEverySecond();
break; break;
case FUNC_SET_POWER: case FUNC_SET_POWER:
Timeprop_Xdrv_Power(); TimepropXdrvPower();
break; break;
} }
return result; return result;
} }

View File

@ -1,28 +1,26 @@
/* /*
xdrv_49_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
Copyright (C) 2021 Colin Law and Thomas Herrmann
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/** #ifdef USE_PID
* Code to /*********************************************************************************************\
* * Uses the library https://github.com/colinl/process-control.git from Github
* Usage: * In user_config_override.h include code as follows:
* Place this file in the sonoff folder.
* Clone the library https://github.com/colinl/process-control.git from Github
* into a subfolder of lib.
* If you want to use a time proportioned relay output with this then also get
* xdrv_49_timeprop.ino
* In user_config.h or user_config_override.h include code as follows:
#define USE_PID // include the pid feature (+4.3k) #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 #define PID_SETPOINT 19.5 // Setpoint value. This is the process value that the process is
@ -113,7 +111,7 @@
// If not using the sensor then you can supply process values via MQTT using // If not using the sensor then you can supply process values via MQTT using
// cmnd PidPv // cmnd PidPv
#define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the #define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the
// number of the shutter here. Otherwise leave this commented out // number of the shutter here. Otherwise leave this commented out
#define PID_REPORT_MORE_SETTINGS // If defined, the SENSOR output will provide more extensive json #define PID_REPORT_MORE_SETTINGS // If defined, the SENSOR output will provide more extensive json
@ -127,13 +125,44 @@
* Help with using the PID algorithm and with loop tuning can be found at * 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 * 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 * This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on
* the code there and the tuning techique described there should work just the same. * the code there and the tuning technique described there should work just the same.
\*********************************************************************************************/
* #ifndef PID_SETPOINT
**/ #define PID_SETPOINT 19.5 // [PidSp] Setpoint value.
#endif
#ifndef PID_PROPBAND
#define PID_PROPBAND 5 // [PidPb] Proportional band in process units (eg degrees).
#endif
#ifndef PID_INTEGRAL_TIME
#define PID_INTEGRAL_TIME 1800 // [PidTi] Integral time seconds.
#endif
#ifndef PID_DERIVATIVE_TIME
#define PID_DERIVATIVE_TIME 15 // [PidTd] Derivative time seconds.
#endif
#ifndef PID_INITIAL_INT
#define PID_INITIAL_INT 0.5 // Initial integral value (0:1).
#endif
#ifndef PID_MAX_INTERVAL
#define PID_MAX_INTERVAL 300 // [PidMaxInterval] This is the maximum time in seconds between samples.
#endif
#ifndef PID_DERIV_SMOOTH_FACTOR
#define PID_DERIV_SMOOTH_FACTOR 3 // [PidDSmooth]
#endif
#ifndef PID_AUTO
#define PID_AUTO 1 // [PidAuto] Auto mode 1 or 0 (for manual).
#endif
#ifndef PID_MANUAL_POWER
#define PID_MANUAL_POWER 0 // [PidManualPower] Power output when in manual mode or fallback mode.
#endif
#ifndef PID_UPDATE_SECS
#define PID_UPDATE_SECS 0 // [PidUpdateSecs] How often to run the pid algorithm
#endif
#define PID_USE_TIMPROP 1 // To disable this feature leave this undefined
#ifdef USE_PID //#define PID_USE_LOCAL_SENSOR // [PidPv] If defined then the local sensor will be used for pv.
//#define PID_SHUTTER 1 // Number of the shutter here. Otherwise leave this commented out
#define PID_REPORT_MORE_SETTINGS // If defined, the SENSOR output will provide more extensive json
#include "PID.h" #include "PID.h"
@ -166,11 +195,11 @@ const char kPIDCommands[] PROGMEM = D_PRFX_PID "|" // Prefix
; ;
void (* const PIDCommand[])(void) PROGMEM = { void (* const PIDCommand[])(void) PROGMEM = {
&CmndSetPv, &CmndSetPv,
&CmndSetSp, &CmndSetSp,
&CmndSetPb, &CmndSetPb,
&CmndSetTi, &CmndSetTi,
&cmndsetTd, &CmndSetTd,
&CmndSetInitialInt, &CmndSetInitialInt,
&CmndSetDSmooth, &CmndSetDSmooth,
&CmndSetAuto, &CmndSetAuto,
@ -179,32 +208,33 @@ void (* const PIDCommand[])(void) PROGMEM = {
&CmndSetUpdateSecs &CmndSetUpdateSecs
}; };
static PID pid; struct {
static int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run PID pid;
static int max_interval = PID_MAX_INTERVAL; int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run
static unsigned long last_pv_update_secs = 0; int max_interval = PID_MAX_INTERVAL;
static bool run_pid_now = false; // tells PID_Every_Second to run the pid algorithm unsigned long last_pv_update_secs = 0;
bool run_pid_now = false; // tells PID_Every_Second to run the pid algorithm
long current_time_secs = 0; // a counter that counts seconds since initialisation
} Pid;
static long pid_current_time_secs = 0; // a counter that counts seconds since initialisation void PIDInit()
void PID_Init()
{ {
pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT, Pid.pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT,
PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER ); PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER );
} }
void PID_Every_Second() { void PIDEverySecond() {
static int sec_counter = 0; static int sec_counter = 0;
pid_current_time_secs++; // increment time 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 // run the pid algorithm if Pid.run_pid_now is true or if the right number of seconds has passed or if too long has
// elapsed since last pv update. If too long has elapsed the the algorithm will deal with that. // 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)) { if (Pid.run_pid_now || Pid.current_time_secs - Pid.last_pv_update_secs > Pid.max_interval || (Pid.update_secs != 0 && sec_counter++ % Pid.update_secs == 0)) {
run_pid(); PIDRun();
run_pid_now = false; Pid.run_pid_now = false;
} }
} }
void PID_Show_Sensor() { void PIDShowSensor() {
// Called each time new sensor data available, data in mqtt data in same format // Called each time new sensor data available, data in mqtt data in same format
// as published in tele/SENSOR // as published in tele/SENSOR
// Update period is specified in TELE_PERIOD // Update period is specified in TELE_PERIOD
@ -212,13 +242,13 @@ void PID_Show_Sensor() {
const float temperature = TasmotaGlobal.temperature_celsius; const float temperature = TasmotaGlobal.temperature_celsius;
// pass the value to the pid alogorithm to use as current pv // pass the value to the pid alogorithm to use as current pv
last_pv_update_secs = pid_current_time_secs; Pid.last_pv_update_secs = Pid.current_time_secs;
pid.setPv(temperature, last_pv_update_secs); Pid.pid.setPv(temperature, Pid.last_pv_update_secs);
// also trigger running the pid algorithm if we have been told to run it each pv sample // also trigger running the pid algorithm if we have been told to run it each pv sample
if (update_secs == 0) { if (Pid.update_secs == 0) {
// this runs it at the next second // this runs it at the next second
run_pid_now = true; Pid.run_pid_now = true;
} }
} else { } else {
AddLog_P(LOG_LEVEL_ERROR, PSTR("PID: No local temperature sensor found")); AddLog_P(LOG_LEVEL_ERROR, PSTR("PID: No local temperature sensor found"));
} }
@ -234,69 +264,69 @@ void PID_Show_Sensor() {
/* } XdrvMailbox; */ /* } XdrvMailbox; */
void CmndSetPv(void) { void CmndSetPv(void) {
last_pv_update_secs = pid_current_time_secs; Pid.last_pv_update_secs = Pid.current_time_secs;
pid.setPv(atof(XdrvMailbox.data), last_pv_update_secs); Pid.pid.setPv(atof(XdrvMailbox.data), Pid.last_pv_update_secs);
// also trigger running the pid algorithm if we have been told to run it each pv sample // also trigger running the pid algorithm if we have been told to run it each pv sample
if (update_secs == 0) { if (Pid.update_secs == 0) {
// this runs it at the next second // this runs it at the next second
run_pid_now = true; Pid.run_pid_now = true;
} }
} }
void CmndSetSp(void) { void CmndSetSp(void) {
pid.setSp(atof(XdrvMailbox.data)); Pid.pid.setSp(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data));
} }
void CmndSetPb(void) { void CmndSetPb(void) {
pid.setPb(atof(XdrvMailbox.data)); Pid.pid.setPb(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data));
} }
void CmndSetTi(void) { void CmndSetTi(void) {
pid.setTi(atof(XdrvMailbox.data)); Pid.pid.setTi(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data));
} }
void cmndsetTd(void) { void CmndSetTd(void) {
pid.setTd(atof(XdrvMailbox.data)); Pid.pid.setTd(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data));
} }
void CmndSetInitialInt(void) { void CmndSetInitialInt(void) {
pid.setInitialInt(atof(XdrvMailbox.data)); Pid.pid.setInitialInt(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data));
} }
void CmndSetDSmooth(void) { void CmndSetDSmooth(void) {
pid.setDSmooth(atof(XdrvMailbox.data)); Pid.pid.setDSmooth(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data));
} }
void CmndSetAuto(void) { void CmndSetAuto(void) {
pid.setAuto(atoi(XdrvMailbox.data)); Pid.pid.setAuto(atoi(XdrvMailbox.data));
ResponseCmndNumber(atoi(XdrvMailbox.data)); ResponseCmndNumber(atoi(XdrvMailbox.data));
} }
void CmndSetManualPower(void) { void CmndSetManualPower(void) {
pid.setManualPower(atof(XdrvMailbox.data)); Pid.pid.setManualPower(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data)); ResponseCmndNumber(atof(XdrvMailbox.data));
} }
void CmndSetMaxInterval(void) { void CmndSetMaxInterval(void) {
pid.setMaxInterval(atoi(XdrvMailbox.data)); Pid.pid.setMaxInterval(atoi(XdrvMailbox.data));
ResponseCmndNumber(atoi(XdrvMailbox.data)); ResponseCmndNumber(atoi(XdrvMailbox.data));
} }
// case CMND_PID_SETUPDATE_SECS: // case CMND_PID_SETUPDATE_SECS:
// update_secs = atoi(XdrvMailbox.data) ; // Pid.update_secs = atoi(XdrvMailbox.data) ;
// if (update_secs < 0) // if (Pid.update_secs < 0)
// update_secs = 0; // Pid.update_secs = 0;
void CmndSetUpdateSecs(void) { void CmndSetUpdateSecs(void) {
update_secs = (atoi(XdrvMailbox.data)); Pid.update_secs = (atoi(XdrvMailbox.data));
if (update_secs < 0) if (Pid.update_secs < 0)
update_secs = 0; Pid.update_secs = 0;
ResponseCmndNumber(update_secs); ResponseCmndNumber(Pid.update_secs);
} }
void PIDShowValues(void) { void PIDShowValues(void) {
@ -307,61 +337,60 @@ void PIDShowValues(void) {
ResponseAppend_P(PSTR(",\"PID\":{")); ResponseAppend_P(PSTR(",\"PID\":{"));
// #define D_CMND_PID_SETPV "Pv" // #define D_CMND_PID_SETPV "Pv"
d_buf = pid.getPv(); d_buf = Pid.pid.getPv();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidPv\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidPv\":%s,"), str_buf);
// #define D_CMND_PID_SETSETPOINT "Sp" // #define D_CMND_PID_SETSETPOINT "Sp"
d_buf = pid.getSp(); d_buf = Pid.pid.getSp();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidSp\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidSp\":%s,"), str_buf);
#ifdef PID_REPORT_MORE_SETTINGS #ifdef PID_REPORT_MORE_SETTINGS
// #define D_CMND_PID_SETPROPBAND "Pb" // #define D_CMND_PID_SETPROPBAND "Pb"
d_buf = pid.getPb(); d_buf = Pid.pid.getPb();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidPb\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidPb\":%s,"), str_buf);
// #define D_CMND_PID_SETINTEGRAL_TIME "Ti" // #define D_CMND_PID_SETINTEGRAL_TIME "Ti"
d_buf = pid.getTi(); d_buf = Pid.pid.getTi();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidTi\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidTi\":%s,"), str_buf);
// #define D_CMND_PID_SETDERIVATIVE_TIME "Td" // #define D_CMND_PID_SETDERIVATIVE_TIME "Td"
d_buf = pid.getTd(); d_buf = Pid.pid.getTd();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidTd\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidTd\":%s,"), str_buf);
// #define D_CMND_PID_SETINITIAL_INT "Initint" // #define D_CMND_PID_SETINITIAL_INT "Initint"
d_buf = pid.getInitialInt(); d_buf = Pid.pid.getInitialInt();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidInitialInt\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidInitialInt\":%s,"), str_buf);
// #define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "DSmooth" // #define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "DSmooth"
d_buf = pid.getDSmooth(); d_buf = Pid.pid.getDSmooth();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidDSmooth\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidDSmooth\":%s,"), str_buf);
// #define D_CMND_PID_SETAUTO "Auto" // #define D_CMND_PID_SETAUTO "Auto"
chr_buf = pid.getAuto(); chr_buf = Pid.pid.getAuto();
ResponseAppend_P(PSTR("\"PidAuto\":%d,"), chr_buf); ResponseAppend_P(PSTR("\"PidAuto\":%d,"), chr_buf);
// #define D_CMND_PID_SETMANUAL_POWER "ManualPower" // #define D_CMND_PID_SETMANUAL_POWER "ManualPower"
d_buf = pid.getManualPower(); d_buf = Pid.pid.getManualPower();
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidManualPower\":%s,"), str_buf); ResponseAppend_P(PSTR("\"PidManualPower\":%s,"), str_buf);
// #define D_CMND_PID_SETMAX_INTERVAL "MaxInterval" // #define D_CMND_PID_SETMAX_INTERVAL "MaxInterval"
i_buf = pid.getMaxInterval(); i_buf = Pid.pid.getMaxInterval();
ResponseAppend_P(PSTR("\"PidMaxInterval\":%d,"), i_buf); ResponseAppend_P(PSTR("\"PidMaxInterval\":%d,"), i_buf);
// #define D_CMND_PID_SETUPDATE_SECS "UpdateSecs" // #define D_CMND_PID_SETUPDATE_SECS "UpdateSecs"
ResponseAppend_P(PSTR("\"PidUpdateSecs\":%d,"), update_secs); ResponseAppend_P(PSTR("\"PidUpdateSecs\":%d,"), Pid.update_secs);
#endif // PID_REPORT_MORE_SETTINGS #endif // PID_REPORT_MORE_SETTINGS
// The actual power value // The actual power value
d_buf = pid.tick(pid_current_time_secs); d_buf = Pid.pid.tick(Pid.current_time_secs);
dtostrfd(d_buf, 2, str_buf); dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidPower\":%s"), str_buf); ResponseAppend_P(PSTR("\"PidPower\":%s"), str_buf);
ResponseAppend_P(PSTR("}")); ResponseAppend_P(PSTR("}"));
} }
static void run_pid() void PIDRun(void) {
{ double power = Pid.pid.tick(Pid.current_time_secs);
double power = pid.tick(pid_current_time_secs);
#ifdef PID_BACKWARD_COMPATIBLE #ifdef PID_BACKWARD_COMPATIBLE
// This part is left inside to regularly publish the PID Power via // This part is left inside to regularly publish the PID Power via
// `%topic%/PID {"power":"0.000"}` // `%topic%/PID {"power":"0.000"}`
@ -372,14 +401,14 @@ static void run_pid()
#endif // PID_BACKWARD_COMPATIBLE #endif // PID_BACKWARD_COMPATIBLE
#if defined PID_SHUTTER #if defined PID_SHUTTER
// send output as a position from 0-100 to defined shutter // send output as a position from 0-100 to defined shutter
int pos = power * 100; int pos = power * 100;
ShutterSetPosition(PID_SHUTTER, pos); ShutterSetPosition(PID_SHUTTER, pos);
#endif //PID_SHUTTER #endif //PID_SHUTTER
#if defined PID_USE_TIMPROP #if defined PID_USE_TIMPROP
// send power to appropriate timeprop output // send power to appropriate timeprop output
Timeprop_Set_Power( PID_USE_TIMPROP-1, power ); TimepropSetPower( PID_USE_TIMPROP-1, power );
#endif // PID_USE_TIMPROP #endif // PID_USE_TIMPROP
} }
@ -389,29 +418,28 @@ static void run_pid()
#define XDRV_49 49 #define XDRV_49 49
bool Xdrv49(byte function) bool Xdrv49(byte function) {
{
bool result = false; bool result = false;
switch (function) { switch (function) {
case FUNC_INIT: case FUNC_INIT:
PID_Init(); PIDInit();
break; break;
case FUNC_EVERY_SECOND: case FUNC_EVERY_SECOND:
PID_Every_Second(); PIDEverySecond();
break; break;
case FUNC_SHOW_SENSOR: case FUNC_SHOW_SENSOR:
// only use this if the pid loop is to use the local sensor for pv // only use this if the pid loop is to use the local sensor for pv
#if defined PID_USE_LOCAL_SENSOR #if defined PID_USE_LOCAL_SENSOR
PID_Show_Sensor(); PIDShowSensor();
#endif // PID_USE_LOCAL_SENSOR #endif // PID_USE_LOCAL_SENSOR
break; break;
case FUNC_COMMAND: case FUNC_COMMAND:
result = DecodeCommand(kPIDCommands, PIDCommand); result = DecodeCommand(kPIDCommands, PIDCommand);
break; break;
case FUNC_JSON_APPEND: case FUNC_JSON_APPEND:
PIDShowValues(); PIDShowValues();
break; break;
} }
return result; return result;
} }