Merge pull request #10 from arendst/development

update dev branch
This commit is contained in:
Vic 2021-01-07 15:17:20 +00:00 committed by GitHub
commit 57507e45c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1277 additions and 62 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

@ -0,0 +1,234 @@
/**
* 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;
}
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);
}

View File

@ -0,0 +1,101 @@
/**
* 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 );
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;
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

View File

@ -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;
}

View File

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

View File

@ -28,7 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE. * Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele. * Use online command Prefix to translate cmnd, stat and tele.
* *
* Updated until v9.2.0.1 * Updated until v9.2.0.3
\*********************************************************************/ \*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English) //#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -875,9 +875,9 @@
#define D_SCRIPT_UPLOAD_FILES "Upload Dateien" #define D_SCRIPT_UPLOAD_FILES "Upload Dateien"
//xdrv_50_filesystem.ino //xdrv_50_filesystem.ino
#define D_MANAGE_FILE_SYSTEM "Manage File system" #define D_MANAGE_FILE_SYSTEM "Verwalte Dateisystem"
#define D_FS_SIZE "Size" #define D_FS_SIZE "Größe"
#define D_FS_FREE "Free" #define D_FS_FREE "Frei"
//xsns_67_as3935.ino //xsns_67_as3935.ino
#define D_AS3935_GAIN "Umgebung:" #define D_AS3935_GAIN "Umgebung:"

View File

@ -28,7 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE. * Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele. * Use online command Prefix to translate cmnd, stat and tele.
* *
* Updated until v9.1.0.2 * Updated until v9.2.0.3
\*********************************************************************/ \*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English) #define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -461,30 +461,30 @@
#define D_KNX_RX_SCENE "KNX ESCENA RX" #define D_KNX_RX_SCENE "KNX ESCENA RX"
// xdrv_23_zigbee // xdrv_23_zigbee
#define D_ZIGBEE_PERMITJOIN_ACTIVE "Devices allowed to join" #define D_ZIGBEE_PERMITJOIN_ACTIVE "Dispositivos permitidos a unirse"
#define D_ZIGBEE_MAPPING_TITLE "Tasmota Zigbee Mapping" #define D_ZIGBEE_MAPPING_TITLE "Mapeo de Tasmota Zigbee"
#define D_ZIGBEE_NOT_STARTED "Zigbee not started" #define D_ZIGBEE_NOT_STARTED "Zigbee no iniciado"
#define D_ZIGBEE_MAPPING_IN_PROGRESS_SEC "Mapping in progress (%d s. remaining)" #define D_ZIGBEE_MAPPING_IN_PROGRESS_SEC "Mapeo en progreso (%d s. restantes)"
#define D_ZIGBEE_MAPPING_NOT_PRESENT "No mapping" #define D_ZIGBEE_MAPPING_NOT_PRESENT "Sin mapeo"
#define D_ZIGBEE_MAP_REFRESH "Zigbee Map Refresh" #define D_ZIGBEE_MAP_REFRESH "Actualizar Mapa Zigbee"
#define D_ZIGBEE_MAP "Zigbee Map" #define D_ZIGBEE_MAP "Mapa Zigbee"
#define D_ZIGBEE_PERMITJOIN "Zigbee Permit Join" #define D_ZIGBEE_PERMITJOIN "Permitir unirse a Zigbee"
#define D_ZIGBEE_GENERATE_KEY "generating random Zigbee network key" #define D_ZIGBEE_GENERATE_KEY "Generando una clave aleatoria de red Zigbee"
#define D_ZIGBEE_UNKNOWN_DEVICE "Unknown device" #define D_ZIGBEE_UNKNOWN_DEVICE "Dispositivo desconocido"
#define D_ZIGBEE_UNKNOWN_ATTRIBUTE "Unknown attribute" #define D_ZIGBEE_UNKNOWN_ATTRIBUTE "Atributo desconocido"
#define D_ZIGBEE_INVALID_PARAM "Invalid parameter" #define D_ZIGBEE_INVALID_PARAM "Parámetro inválido"
#define D_ZIGBEE_MISSING_PARAM "Missing parameters" #define D_ZIGBEE_MISSING_PARAM "Parámetros faltantes"
#define D_ZIGBEE_UNKNWON_ATTRIBUTE "Unknown attribute name (ignored): %s" #define D_ZIGBEE_UNKNWON_ATTRIBUTE "Nombre de atributo desconocido (ignorado): %s"
#define D_ZIGBEE_TOO_MANY_CLUSTERS "No more than one cluster id per command" #define D_ZIGBEE_TOO_MANY_CLUSTERS "No mas de un id de cluster por comando"
#define D_ZIGBEE_WRONG_DELIMITER "Wrong delimiter for payload" #define D_ZIGBEE_WRONG_DELIMITER "Delimitador incorrecto para payload"
#define D_ZIGBEE_UNRECOGNIZED_COMMAND "Unrecognized zigbee command: %s" #define D_ZIGBEE_UNRECOGNIZED_COMMAND "Comando zigbee no reconocido: %s"
#define D_ZIGBEE_TOO_MANY_COMMANDS "Only 1 command allowed (%d)" #define D_ZIGBEE_TOO_MANY_COMMANDS "Solo un comando es permitido (%d)"
#define D_ZIGBEE_NO_ATTRIBUTE "No attribute in list" #define D_ZIGBEE_NO_ATTRIBUTE "No hay atributos en la lista"
#define D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE "Unsupported attribute type" #define D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE "Atributo no soportado"
#define D_ZIGBEE_JSON_REQUIRED "Config requires JSON objects" #define D_ZIGBEE_JSON_REQUIRED "La configuración debe ser en JSON"
#define D_ZIGBEE_RESET_1_OR_2 "1 or 2 to reset" #define D_ZIGBEE_RESET_1_OR_2 "1 o 2 para resetear"
#define D_ZIGBEE_EEPROM_FOUND_AT_ADDRESS "ZBBridge EEPROM found at address" #define D_ZIGBEE_EEPROM_FOUND_AT_ADDRESS "Encontrada EEPROM de ZBBridge en"
#define D_ZIGBEE_RANDOMIZING_ZBCONFIG "Randomizing Zigbee parameters, please check with 'ZbConfig'" #define D_ZIGBEE_RANDOMIZING_ZBCONFIG "Configurando parámetros Zigbee de forma aleatoria. Usar 'ZbConfig' para revisarlos."
// xdrv_03_energy.ino // xdrv_03_energy.ino
#define D_ENERGY_TODAY "Energía Hoy" #define D_ENERGY_TODAY "Energía Hoy"
@ -875,9 +875,9 @@
#define D_SCRIPT_UPLOAD_FILES "Cargar Archivos" #define D_SCRIPT_UPLOAD_FILES "Cargar Archivos"
//xdrv_50_filesystem.ino //xdrv_50_filesystem.ino
#define D_MANAGE_FILE_SYSTEM "Manage File system" #define D_MANAGE_FILE_SYSTEM "Explorar Archivos"
#define D_FS_SIZE "Size" #define D_FS_SIZE "Tamaño"
#define D_FS_FREE "Free" #define D_FS_FREE "Libre"
//xsns_67_as3935.ino //xsns_67_as3935.ino
#define D_AS3935_GAIN "Ganancia:" #define D_AS3935_GAIN "Ganancia:"

View File

@ -1,7 +1,7 @@
/* /*
it-IT.h - localization for Italian - Italy for Tasmota it-IT.h - localization for Italian - Italy for Tasmota
Copyright (C) 2021 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 24.12.2020 Copyright (C) 2021 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 07.01.2021
This program is free software: you can redistribute it and/or modify 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
@ -779,7 +779,7 @@
#define D_SENSOR_ST7789_DC "ST7789 - DC" #define D_SENSOR_ST7789_DC "ST7789 - DC"
#define D_SENSOR_SSD1331_CS "SSD1331 - CS" #define D_SENSOR_SSD1331_CS "SSD1331 - CS"
#define D_SENSOR_SSD1331_DC "SSD1331 - DC" #define D_SENSOR_SSD1331_DC "SSD1331 - DC"
#define D_SENSOR_SDCARD_CS "SDCard - CS" #define D_SENSOR_SDCARD_CS "Scheda SD - CS"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"
@ -875,9 +875,9 @@
#define D_SCRIPT_UPLOAD_FILES "Carica file" #define D_SCRIPT_UPLOAD_FILES "Carica file"
//xdrv_50_filesystem.ino //xdrv_50_filesystem.ino
#define D_MANAGE_FILE_SYSTEM "Manage File system" #define D_MANAGE_FILE_SYSTEM "Gestione File system"
#define D_FS_SIZE "Size" #define D_FS_SIZE "Dimensione"
#define D_FS_FREE "Free" #define D_FS_FREE "Liberi"
//xsns_67_as3935.ino //xsns_67_as3935.ino
#define D_AS3935_GAIN "guadagno:" #define D_AS3935_GAIN "guadagno:"

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,10 +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 --------------------------- // -- PID and Timeprop ------------------------------
//#define USE_PROMETHEUS // Add support for https://prometheus.io/ metrics exporting over HTTP /metrics endpoint //#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
// -- End of general directives ------------------- //#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 ---------------------
/*********************************************************************************************\ /*********************************************************************************************\
* 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

@ -2244,7 +2244,7 @@ chknext:
if (!strncmp(vname, "fsi(", 4)) { if (!strncmp(vname, "fsi(", 4)) {
lp = GetNumericArgument(lp + 4, OPER_EQU, &fvar, 0); lp = GetNumericArgument(lp + 4, OPER_EQU, &fvar, 0);
fvar = ufs_fsinfo(fvar); fvar = ufs_fsinfo(fvar, 0);
lp++; lp++;
len = 0; len = 0;
goto exit; goto exit;

View File

@ -0,0 +1,190 @@
/*
xdrv_48_timeprop.ino - Timeprop support for Sonoff-Tasmota
Copyright (C) 2021 Colin Law and Thomas Herrmann
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_TIMEPROP
/*********************************************************************************************\
* 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
\*********************************************************************************************/
#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
#include "Timeprop.h"
struct {
Timeprop timeprops[TIMEPROP_NUM_OUTPUTS];
int relay_nos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS};
long current_relay_states = 0; // current actual relay states. Bit 0 first relay
long current_time_secs = 0; // a counter that counts seconds since initialisation
} Tprop;
/* call this from elsewhere if required to set the power value for one of the timeprop instances */
/* index specifies which one, 0 up */
void TimepropSetPower(int index, float power) {
if (index >= 0 && index < TIMEPROP_NUM_OUTPUTS) {
Tprop.timeprops[index].setPower( power, Tprop.current_time_secs);
}
}
void TimepropInit(void) {
// AddLog_P(LOG_LEVEL_INFO, PSTR("TPR: Timeprop Init"));
int cycleTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_CYCLETIMES};
int deadTimes[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_DEADTIMES};
int opInverts[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_OPINVERTS};
int fallbacks[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_FALLBACK_POWERS};
int maxIntervals[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_MAX_UPDATE_INTERVALS};
for (int i = 0; i < TIMEPROP_NUM_OUTPUTS; i++) {
Tprop.timeprops[i].initialise(cycleTimes[i], deadTimes[i], opInverts[i], fallbacks[i],
maxIntervals[i], Tprop.current_time_secs);
}
}
void TimepropEverySecond(void) {
Tprop.current_time_secs++; // increment time
for (int i=0; i<TIMEPROP_NUM_OUTPUTS; i++) {
int newState = Tprop.timeprops[i].tick(Tprop.current_time_secs);
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
ExecuteCommandPower(Tprop.relay_nos[i], newState,SRC_IGNORE);
}
}
}
// called by the system each time a relay state is changed
void TimepropXdrvPower(void) {
// 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
// able to test that
Tprop.current_relay_states = XdrvMailbox.index;
}
/* struct XDRVMAILBOX { */
/* uint16_t valid; */
/* uint16_t index; */
/* uint16_t data_len; */
/* int16_t payload; */
/* char *topic; */
/* char *data; */
/* } XdrvMailbox; */
// To get here post with topic cmnd/timeprop_setpower_n where n is index into Tprop.timeprops 0:7
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
#define XDRV_48 48
bool Xdrv48(byte function) {
bool result = false;
switch (function) {
case FUNC_INIT:
TimepropInit();
break;
case FUNC_EVERY_SECOND:
TimepropEverySecond();
break;
case FUNC_SET_POWER:
TimepropXdrvPower();
break;
}
return result;
}
#endif // USE_TIMEPROP

446
tasmota/xdrv_49_pid.ino Normal file
View File

@ -0,0 +1,446 @@
/*
xdrv_49_pid.ino - PID algorithm plugin for Sonoff-Tasmota
Copyright (C) 2021 Colin Law and Thomas Herrmann
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_PID
/*********************************************************************************************\
* Uses the library https://github.com/colinl/process-control.git from Github
* In user_config_override.h include code as follows:
#define USE_PID // include the pid feature (+4.3k)
#define PID_SETPOINT 19.5 // Setpoint value. This is the process value that the process is
// aiming for.
// 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 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
// 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 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.
// 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 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
// 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 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
// 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 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 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 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.
// 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 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
// 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_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
// 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 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_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
// `%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
* 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
//#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"
/* 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"
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;
;
void (* const PIDCommand[])(void) PROGMEM = {
&CmndSetPv,
&CmndSetSp,
&CmndSetPb,
&CmndSetTi,
&CmndSetTd,
&CmndSetInitialInt,
&CmndSetDSmooth,
&CmndSetAuto,
&CmndSetManualPower,
&CmndSetMaxInterval,
&CmndSetUpdateSecs
};
struct {
PID pid;
int update_secs = PID_UPDATE_SECS <= 0 ? 0 : PID_UPDATE_SECS; // how often (secs) the pid alogorithm is run
int max_interval = PID_MAX_INTERVAL;
unsigned long last_pv_update_secs = 0;
bool run_pid_now = false; // tells PID_Every_Second to run the pid algorithm
long current_time_secs = 0; // a counter that counts seconds since initialisation
} Pid;
void PIDInit()
{
Pid.pid.initialise( PID_SETPOINT, PID_PROPBAND, PID_INTEGRAL_TIME, PID_DERIVATIVE_TIME, PID_INITIAL_INT,
PID_MAX_INTERVAL, PID_DERIV_SMOOTH_FACTOR, PID_AUTO, PID_MANUAL_POWER );
}
void PIDEverySecond() {
static int sec_counter = 0;
Pid.current_time_secs++; // increment time
// run the pid algorithm if Pid.run_pid_now is true or if the right number of seconds has passed or if too long has
// elapsed since last pv update. If too long has elapsed the the algorithm will deal with that.
if (Pid.run_pid_now || Pid.current_time_secs - Pid.last_pv_update_secs > Pid.max_interval || (Pid.update_secs != 0 && sec_counter++ % Pid.update_secs == 0)) {
PIDRun();
Pid.run_pid_now = false;
}
}
void PIDShowSensor() {
// Called each time new sensor data available, data in mqtt data in same format
// as published in tele/SENSOR
// Update period is specified in TELE_PERIOD
if (!isnan(TasmotaGlobal.temperature_celsius)) {
const float temperature = TasmotaGlobal.temperature_celsius;
// pass the value to the pid alogorithm to use as current pv
Pid.last_pv_update_secs = Pid.current_time_secs;
Pid.pid.setPv(temperature, Pid.last_pv_update_secs);
// also trigger running the pid algorithm if we have been told to run it each pv sample
if (Pid.update_secs == 0) {
// this runs it at the next second
Pid.run_pid_now = true;
}
} else {
AddLog_P(LOG_LEVEL_ERROR, PSTR("PID: No local temperature sensor found"));
}
}
/* struct XDRVMAILBOX { */
/* uint16_t valid; */
/* uint16_t index; */
/* uint16_t data_len; */
/* int16_t payload; */
/* char *topic; */
/* char *data; */
/* } XdrvMailbox; */
void CmndSetPv(void) {
Pid.last_pv_update_secs = Pid.current_time_secs;
Pid.pid.setPv(atof(XdrvMailbox.data), Pid.last_pv_update_secs);
// also trigger running the pid algorithm if we have been told to run it each pv sample
if (Pid.update_secs == 0) {
// this runs it at the next second
Pid.run_pid_now = true;
}
}
void CmndSetSp(void) {
Pid.pid.setSp(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data));
}
void CmndSetPb(void) {
Pid.pid.setPb(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data));
}
void CmndSetTi(void) {
Pid.pid.setTi(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data));
}
void CmndSetTd(void) {
Pid.pid.setTd(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data));
}
void CmndSetInitialInt(void) {
Pid.pid.setInitialInt(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data));
}
void CmndSetDSmooth(void) {
Pid.pid.setDSmooth(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data));
}
void CmndSetAuto(void) {
Pid.pid.setAuto(atoi(XdrvMailbox.data));
ResponseCmndNumber(atoi(XdrvMailbox.data));
}
void CmndSetManualPower(void) {
Pid.pid.setManualPower(atof(XdrvMailbox.data));
ResponseCmndNumber(atof(XdrvMailbox.data));
}
void CmndSetMaxInterval(void) {
Pid.pid.setMaxInterval(atoi(XdrvMailbox.data));
ResponseCmndNumber(atoi(XdrvMailbox.data));
}
// case CMND_PID_SETUPDATE_SECS:
// Pid.update_secs = atoi(XdrvMailbox.data) ;
// if (Pid.update_secs < 0)
// Pid.update_secs = 0;
void CmndSetUpdateSecs(void) {
Pid.update_secs = (atoi(XdrvMailbox.data));
if (Pid.update_secs < 0)
Pid.update_secs = 0;
ResponseCmndNumber(Pid.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.pid.getPv();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidPv\":%s,"), str_buf);
// #define D_CMND_PID_SETSETPOINT "Sp"
d_buf = Pid.pid.getSp();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidSp\":%s,"), str_buf);
#ifdef PID_REPORT_MORE_SETTINGS
// #define D_CMND_PID_SETPROPBAND "Pb"
d_buf = Pid.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.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.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.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.pid.getDSmooth();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidDSmooth\":%s,"), str_buf);
// #define D_CMND_PID_SETAUTO "Auto"
chr_buf = Pid.pid.getAuto();
ResponseAppend_P(PSTR("\"PidAuto\":%d,"), chr_buf);
// #define D_CMND_PID_SETMANUAL_POWER "ManualPower"
d_buf = Pid.pid.getManualPower();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidManualPower\":%s,"), str_buf);
// #define D_CMND_PID_SETMAX_INTERVAL "MaxInterval"
i_buf = Pid.pid.getMaxInterval();
ResponseAppend_P(PSTR("\"PidMaxInterval\":%d,"), i_buf);
// #define D_CMND_PID_SETUPDATE_SECS "UpdateSecs"
ResponseAppend_P(PSTR("\"PidUpdateSecs\":%d,"), Pid.update_secs);
#endif // PID_REPORT_MORE_SETTINGS
// The actual power value
d_buf = Pid.pid.tick(Pid.current_time_secs);
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidPower\":%s"), str_buf);
ResponseAppend_P(PSTR("}"));
}
void PIDRun(void) {
double power = Pid.pid.tick(Pid.current_time_secs);
#ifdef PID_BACKWARD_COMPATIBLE
// This part is left inside to regularly publish the PID Power via
// `%topic%/PID {"power":"0.000"}`
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);
#endif // PID_BACKWARD_COMPATIBLE
#if defined PID_SHUTTER
// send output as a position from 0-100 to defined shutter
int pos = power * 100;
ShutterSetPosition(PID_SHUTTER, pos);
#endif //PID_SHUTTER
#if defined PID_USE_TIMPROP
// send power to appropriate timeprop output
TimepropSetPower( PID_USE_TIMPROP-1, power );
#endif // PID_USE_TIMPROP
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
#define XDRV_49 49
bool Xdrv49(byte function) {
bool result = false;
switch (function) {
case FUNC_INIT:
PIDInit();
break;
case FUNC_EVERY_SECOND:
PIDEverySecond();
break;
case FUNC_SHOW_SENSOR:
// only use this if the pid loop is to use the local sensor for pv
#if defined PID_USE_LOCAL_SENSOR
PIDShowSensor();
#endif // PID_USE_LOCAL_SENSOR
break;
case FUNC_COMMAND:
result = DecodeCommand(kPIDCommands, PIDCommand);
break;
case FUNC_JSON_APPEND:
PIDShowValues();
break;
}
return result;
}
#endif // USE_PID

View File

@ -48,6 +48,8 @@ The driver enabled by #define USE_UFILESYS
#define SDCARD_CS_PIN 4 #define SDCARD_CS_PIN 4
#endif #endif
#define FFS_2
#ifdef ESP8266 #ifdef ESP8266
#include <LittleFS.h> #include <LittleFS.h>
#include <SPI.h> #include <SPI.h>
@ -73,12 +75,16 @@ The driver enabled by #define USE_UFILESYS
FS *ufsp; FS *ufsp;
// flash file system pointer on esp32 // flash file system pointer on esp32
FS *ffsp; FS *ffsp;
// local pointer for file managment
FS *dfsp;
char ufs_path[48]; char ufs_path[48];
File ufs_upload_file; File ufs_upload_file;
uint8_t ufs_dir;
// 0 = none, 1 = SD, 2 = ffat, 3 = littlefs // 0 = none, 1 = SD, 2 = ffat, 3 = littlefs
// spiffs should be obsolete
uint8_t ufs_type; uint8_t ufs_type;
uint8_t ffs_type;
#define UFS_TNONE 0 #define UFS_TNONE 0
#define UFS_TSDC 1 #define UFS_TSDC 1
#define UFS_TFAT 2 #define UFS_TFAT 2
@ -87,6 +93,7 @@ uint8_t ufs_type;
void UfsInit(void) { void UfsInit(void) {
ufs_type = 0; ufs_type = 0;
ffsp = 0; ffsp = 0;
ufs_dir = 0;
// check for fs options, // check for fs options,
// 1. check for SD card // 1. check for SD card
// 2. check for littlefs or FAT // 2. check for littlefs or FAT
@ -108,6 +115,8 @@ void UfsInit(void) {
ufsp = &SD; ufsp = &SD;
#endif // ESP32 #endif // ESP32
ufs_type = UFS_TSDC; ufs_type = UFS_TSDC;
dfsp = ufsp;
#ifdef FFS_2
// now detect ffs // now detect ffs
ffsp = &LITTLEFS; ffsp = &LITTLEFS;
if (!LITTLEFS.begin()) { if (!LITTLEFS.begin()) {
@ -117,7 +126,13 @@ void UfsInit(void) {
ffsp = 0; ffsp = 0;
return; return;
} }
ffs_type = UFS_TFAT;
ufs_dir = 1;
return;
} }
ffs_type = UFS_TLFS;
ufs_dir = 1;
#endif // FFS_2
return; return;
} }
} }
@ -141,11 +156,13 @@ void UfsInit(void) {
} }
ufs_type = UFS_TFAT; ufs_type = UFS_TFAT;
ffsp = ufsp; ffsp = ufsp;
dfsp = ufsp;
return; return;
} }
#endif // ESP32 #endif // ESP32
ufs_type = UFS_TLFS; ufs_type = UFS_TLFS;
ffsp = ufsp; ffsp = ufsp;
dfsp = ufsp;
return; return;
} }
@ -204,18 +221,24 @@ bool TfsLoadFile(const char *fname, uint8_t *buf, uint32_t len) {
return true; return true;
} }
uint32_t ufs_fsinfo(uint32_t sel) { uint32_t ufs_fsinfo(uint32_t sel, uint32_t type) {
uint32_t result = 0; uint32_t result = 0;
FS *ifsp = ufsp;
uint8_t itype = ufs_type;
if (type) {
ifsp = ffsp;
itype = ffs_type;
}
#ifdef ESP8266 #ifdef ESP8266
FSInfo64 fsinfo; FSInfo64 fsinfo;
#endif // ESP8266 #endif // ESP8266
switch (ufs_type) { switch (itype) {
case UFS_TSDC: case UFS_TSDC:
#ifdef USE_SDCARD #ifdef USE_SDCARD
#ifdef ESP8266 #ifdef ESP8266
ufsp->info64(fsinfo); ifsp->info64(fsinfo);
if (sel == 0) { if (sel == 0) {
result = fsinfo.totalBytes; result = fsinfo.totalBytes;
} else { } else {
@ -234,7 +257,7 @@ uint32_t ufs_fsinfo(uint32_t sel) {
case UFS_TLFS: case UFS_TLFS:
#ifdef ESP8266 #ifdef ESP8266
ufsp->info64(fsinfo); ifsp->info64(fsinfo);
if (sel == 0) { if (sel == 0) {
result = fsinfo.totalBytes; result = fsinfo.totalBytes;
} else { } else {
@ -323,17 +346,17 @@ void (* const kUFSCommand[])(void) PROGMEM = {
&UFS_info, &UFS_type, &UFS_size, &UFS_free}; &UFS_info, &UFS_type, &UFS_size, &UFS_free};
void UFS_info(void) { void UFS_info(void) {
Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}}"), ufs_type, ufs_fsinfo(0), ufs_fsinfo(1)); Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}}"), ufs_type, ufs_fsinfo(0, 0), ufs_fsinfo(1, 0));
} }
void UFS_type(void) { void UFS_type(void) {
ResponseCmndNumber(ufs_type); ResponseCmndNumber(ufs_type);
} }
void UFS_size(void) { void UFS_size(void) {
ResponseCmndNumber(ufs_fsinfo(0)); ResponseCmndNumber(ufs_fsinfo(0, 0));
} }
void UFS_free(void) { void UFS_free(void) {
ResponseCmndNumber(ufs_fsinfo(1)); ResponseCmndNumber(ufs_fsinfo(1, 0));
} }
const char UFS_WEB_DIR[] PROGMEM = const char UFS_WEB_DIR[] PROGMEM =
@ -343,7 +366,15 @@ const char UFS_FORM_FILE_UPLOAD[] PROGMEM =
"<div id='f1' name='f1' style='display:block;'>" "<div id='f1' name='f1' style='display:block;'>"
"<fieldset><legend><b>&nbsp;" D_MANAGE_FILE_SYSTEM "&nbsp;</b></legend>"; "<fieldset><legend><b>&nbsp;" D_MANAGE_FILE_SYSTEM "&nbsp;</b></legend>";
const char UFS_FORM_FILE_UPGc[] PROGMEM = const char UFS_FORM_FILE_UPGc[] PROGMEM =
"<div style='text-align:left;color:#%06x;'>" D_FS_SIZE " %s kB - " D_FS_FREE " %s kB</div>"; "<div style='text-align:left;color:#%06x;'>" D_FS_SIZE " %s kB - " D_FS_FREE " %s kB";
const char UFS_FORM_FILE_UPGc1[] PROGMEM =
" &nbsp;&nbsp;<a href='http://%s/ufsd?dir=%d'>%s</a>";
const char UFS_FORM_FILE_UPGc2[] PROGMEM =
"</div>";
const char UFS_FORM_FILE_UPG[] PROGMEM = const char UFS_FORM_FILE_UPG[] PROGMEM =
"<form method='post' action='ufsu' enctype='multipart/form-data'>" "<form method='post' action='ufsu' enctype='multipart/form-data'>"
"<br><input type='file' name='ufsu'><br>" "<br><input type='file' name='ufsu'><br>"
@ -379,16 +410,34 @@ void UFSdirectory(void) {
} }
} }
if (Webserver->hasArg("dir")) {
String stmp = Webserver->arg("dir");
ufs_dir = atoi(stmp.c_str());
if (ufs_dir == 1) {
dfsp = ufsp;
} else {
if (ffsp) {
dfsp = ffsp;
}
}
}
WSContentStart_P(PSTR(D_MANAGE_FILE_SYSTEM)); WSContentStart_P(PSTR(D_MANAGE_FILE_SYSTEM));
WSContentSendStyle(); WSContentSendStyle();
WSContentSend_P(UFS_FORM_FILE_UPLOAD); WSContentSend_P(UFS_FORM_FILE_UPLOAD);
char ts[16]; char ts[16];
char fs[16]; char fs[16];
UFS_form1000(ufs_fsinfo(0), ts, '.'); UFS_form1000(ufs_fsinfo(0, ufs_dir == 1 ? 0:1), ts, '.');
UFS_form1000(ufs_fsinfo(1), fs, '.'); UFS_form1000(ufs_fsinfo(1, ufs_dir == 1 ? 0:1), fs, '.');
WSContentSend_P(UFS_FORM_FILE_UPGc, WebColor(COL_TEXT), ts, fs); WSContentSend_P(UFS_FORM_FILE_UPGc, WebColor(COL_TEXT), ts, fs);
if (ufs_dir) {
WSContentSend_P(UFS_FORM_FILE_UPGc1, WiFi.localIP().toString().c_str(),ufs_dir == 1 ? 2:1, ufs_dir == 1 ? "UFS":"FFS");
}
WSContentSend_P(UFS_FORM_FILE_UPGc2);
WSContentSend_P(UFS_FORM_FILE_UPG, D_SCRIPT_UPLOAD); WSContentSend_P(UFS_FORM_FILE_UPG, D_SCRIPT_UPLOAD);
WSContentSend_P(UFS_FORM_SDC_DIRa); WSContentSend_P(UFS_FORM_SDC_DIRa);
@ -408,7 +457,7 @@ void UFS_ListDir(char *path, uint8_t depth) {
char format[12]; char format[12];
sprintf(format, "%%-%ds", 24 - depth); sprintf(format, "%%-%ds", 24 - depth);
File dir = ufsp->open(path, UFS_FILE_READ); File dir = dfsp->open(path, UFS_FILE_READ);
if (dir) { if (dir) {
dir.rewindDirectory(); dir.rewindDirectory();
if (strlen(path)>1) { if (strlen(path)>1) {
@ -478,12 +527,12 @@ uint8_t UFS_DownloadFile(char *file) {
File download_file; File download_file;
WiFiClient download_Client; WiFiClient download_Client;
if (!ufsp->exists(file)) { if (!dfsp->exists(file)) {
AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: File not found")); AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: File not found"));
return 0; return 0;
} }
download_file = ufsp->open(file, UFS_FILE_READ); download_file = dfsp->open(file, UFS_FILE_READ);
if (!download_file) { if (!download_file) {
AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: Could not open file")); AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: Could not open file"));
return 0; return 0;
@ -540,8 +589,8 @@ void UFS_Upload(void) {
if (upload.status == UPLOAD_FILE_START) { if (upload.status == UPLOAD_FILE_START) {
char npath[48]; char npath[48];
sprintf(npath, "%s/%s", ufs_path, upload.filename.c_str()); sprintf(npath, "%s/%s", ufs_path, upload.filename.c_str());
ufsp->remove(npath); dfsp->remove(npath);
ufs_upload_file = ufsp->open(npath, UFS_FILE_WRITE); ufs_upload_file = dfsp->open(npath, UFS_FILE_WRITE);
if (!ufs_upload_file) { Web.upload_error = 1; } if (!ufs_upload_file) { Web.upload_error = 1; }
} }
else if (upload.status == UPLOAD_FILE_WRITE) { else if (upload.status == UPLOAD_FILE_WRITE) {

View File

@ -243,7 +243,7 @@ a_features = [[
"USE_EZODO","USE_EZORGB","USE_EZOPMP","USE_AS608", "USE_EZODO","USE_EZORGB","USE_EZOPMP","USE_AS608",
"USE_SHELLY_DIMMER","USE_RC522","USE_FTC532","USE_DISPLAY_EPAPER_42", "USE_SHELLY_DIMMER","USE_RC522","USE_FTC532","USE_DISPLAY_EPAPER_42",
"USE_DISPLAY_ILI9488","USE_DISPLAY_SSD1351","USE_DISPLAY_RA8876","USE_DISPLAY_ST7789", "USE_DISPLAY_ILI9488","USE_DISPLAY_SSD1351","USE_DISPLAY_RA8876","USE_DISPLAY_ST7789",
"USE_DISPLAY_SSD1331","USE_UFILESYS","","", "USE_DISPLAY_SSD1331","USE_UFILESYS","USE_TIMEPROP","USE_PID",
"","","","", "","","","",
"","","","" "","","",""
]] ]]