2020-04-20 21:50:53 +01:00
/*
2020-05-02 21:35:53 +01:00
xdrv_39_thermostat . ino - Thermostat controller for Tasmota
2020-04-20 21:50:53 +01:00
Copyright ( C ) 2020 Javier Arigita
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_THERMOSTAT
# define XDRV_39 39
// Enable/disable debugging
2020-05-03 14:11:19 +01:00
//#define DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
2020-05-20 21:10:49 +01:00
// Enable/disable experimental PI auto-tuning inspired by the Arduino
2020-10-10 14:19:11 +01:00
// Autotune Library by Brett Beauregard
2020-05-14 21:44:32 +01:00
//#define USE_PI_AUTOTUNING // (Ziegler-Nichols closed loop method)
2020-04-20 21:50:53 +01:00
# ifdef DEBUG_THERMOSTAT
2020-05-03 14:11:19 +01:00
# define DOMOTICZ_MAX_IDX 4
2020-04-20 21:50:53 +01:00
# define DOMOTICZ_IDX1 791
# define DOMOTICZ_IDX2 792
2020-05-03 14:11:19 +01:00
# define DOMOTICZ_IDX3 799
# define DOMOTICZ_IDX4 800
# define DOMOTICZ_IDX5 801
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
// Commands
# define D_CMND_THERMOSTATMODESET "ThermostatModeSet"
2020-05-05 19:40:09 +01:00
# define D_CMND_CLIMATEMODESET "ClimateModeSet"
2020-04-20 21:50:53 +01:00
# define D_CMND_TEMPFROSTPROTECTSET "TempFrostProtectSet"
# define D_CMND_CONTROLLERMODESET "ControllerModeSet"
# define D_CMND_INPUTSWITCHSET "InputSwitchSet"
2020-05-03 14:11:19 +01:00
# define D_CMND_INPUTSWITCHUSE "InputSwitchUse"
2020-04-20 21:50:53 +01:00
# define D_CMND_OUTPUTRELAYSET "OutputRelaySet"
# define D_CMND_TIMEALLOWRAMPUPSET "TimeAllowRampupSet"
2020-05-01 20:28:58 +01:00
# define D_CMND_TEMPFORMATSET "TempFormatSet"
2020-04-20 21:50:53 +01:00
# define D_CMND_TEMPMEASUREDSET "TempMeasuredSet"
# define D_CMND_TEMPTARGETSET "TempTargetSet"
# define D_CMND_TEMPMEASUREDGRDREAD "TempMeasuredGrdRead"
# define D_CMND_TEMPSENSNUMBERSET "TempSensNumberSet"
2020-04-29 20:42:20 +01:00
# define D_CMND_SENSORINPUTSET "SensorInputSet"
2020-04-20 21:50:53 +01:00
# define D_CMND_STATEEMERGENCYSET "StateEmergencySet"
# define D_CMND_TIMEMANUALTOAUTOSET "TimeManualToAutoSet"
# define D_CMND_TIMEONLIMITSET "TimeOnLimitSet"
# define D_CMND_PROPBANDSET "PropBandSet"
# define D_CMND_TIMERESETSET "TimeResetSet"
# define D_CMND_TIMEPICYCLESET "TimePiCycleSet"
# define D_CMND_TEMPANTIWINDUPRESETSET "TempAntiWindupResetSet"
# define D_CMND_TEMPHYSTSET "TempHystSet"
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
# define D_CMND_PERFLEVELAUTOTUNE "PerfLevelAutotune"
# endif // USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
# define D_CMND_TIMEMAXACTIONSET "TimeMaxActionSet"
# define D_CMND_TIMEMINACTIONSET "TimeMinActionSet"
# define D_CMND_TIMEMINTURNOFFACTIONSET "TimeMinTurnoffActionSet"
# define D_CMND_TEMPRUPDELTINSET "TempRupDeltInSet"
# define D_CMND_TEMPRUPDELTOUTSET "TempRupDeltOutSet"
# define D_CMND_TIMERAMPUPMAXSET "TimeRampupMaxSet"
# define D_CMND_TIMERAMPUPCYCLESET "TimeRampupCycleSet"
# define D_CMND_TEMPRAMPUPPIACCERRSET "TempRampupPiAccErrSet"
# define D_CMND_TIMEPIPROPORTREAD "TimePiProportRead"
# define D_CMND_TIMEPIINTEGRREAD "TimePiIntegrRead"
# define D_CMND_TIMESENSLOSTSET "TimeSensLostSet"
2020-05-02 19:31:20 +01:00
# define D_CMND_DIAGNOSTICMODESET "DiagnosticModeSet"
2020-05-20 21:10:49 +01:00
# define D_CMND_CTRDUTYCYCLEREAD "CtrDutyCycleRead"
# define D_CMND_ENABLEOUTPUTSET "EnableOutputSet"
2020-04-20 21:50:53 +01:00
enum ThermostatModes { THERMOSTAT_OFF , THERMOSTAT_AUTOMATIC_OP , THERMOSTAT_MANUAL_OP , THERMOSTAT_MODES_MAX } ;
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
enum ControllerModes { CTR_HYBRID , CTR_PI , CTR_RAMP_UP , CTR_PI_AUTOTUNE , CTR_MODES_MAX } ;
enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP , CTR_HYBRID_PI , CTR_HYBRID_PI_AUTOTUNE } ;
enum AutotuneStates { AUTOTUNE_OFF , AUTOTUNE_ON , AUTOTUNE_MAX } ;
enum AutotunePerformanceParam { AUTOTUNE_PERF_FAST , AUTOTUNE_PERF_NORMAL , AUTOTUNE_PERF_SLOW , AUTOTUNE_PERF_MAX } ;
# else
2020-04-20 21:50:53 +01:00
enum ControllerModes { CTR_HYBRID , CTR_PI , CTR_RAMP_UP , CTR_MODES_MAX } ;
enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP , CTR_HYBRID_PI } ;
2020-05-14 21:44:32 +01:00
# endif // USE_PI_AUTOTUNING
2020-05-05 19:40:09 +01:00
enum ClimateModes { CLIMATE_HEATING , CLIMATE_COOLING , CLIMATE_MODES_MAX } ;
2020-04-20 21:50:53 +01:00
enum InterfaceStates { IFACE_OFF , IFACE_ON } ;
2020-05-03 14:11:19 +01:00
enum InputUsage { INPUT_NOT_USED , INPUT_USED } ;
2020-04-20 21:50:53 +01:00
enum CtrCycleStates { CYCLE_OFF , CYCLE_ON } ;
enum EmergencyStates { EMERGENCY_OFF , EMERGENCY_ON } ;
2020-05-01 20:28:58 +01:00
enum SensorType { SENSOR_MQTT , SENSOR_LOCAL , SENSOR_MAX } ;
enum TempFormat { TEMP_CELSIUS , TEMP_FAHRENHEIT } ;
enum TempConvType { TEMP_CONV_ABSOLUTE , TEMP_CONV_RELATIVE } ;
2020-05-02 19:31:20 +01:00
enum DiagnosticModes { DIAGNOSTIC_OFF , DIAGNOSTIC_ON } ;
2020-04-20 21:50:53 +01:00
enum ThermostatSupportedInputSwitches {
THERMOSTAT_INPUT_NONE ,
THERMOSTAT_INPUT_SWT1 = 1 , // Buttons
THERMOSTAT_INPUT_SWT2 ,
THERMOSTAT_INPUT_SWT3 ,
2020-05-20 21:10:49 +01:00
THERMOSTAT_INPUT_SWT4 ,
THERMOSTAT_INPUT_SWT5 ,
THERMOSTAT_INPUT_SWT6 ,
THERMOSTAT_INPUT_SWT7 ,
THERMOSTAT_INPUT_SWT8
2020-04-20 21:50:53 +01:00
} ;
enum ThermostatSupportedOutputRelays {
THERMOSTAT_OUTPUT_NONE ,
THERMOSTAT_OUTPUT_REL1 = 1 , // Relays
THERMOSTAT_OUTPUT_REL2 ,
THERMOSTAT_OUTPUT_REL3 ,
THERMOSTAT_OUTPUT_REL4 ,
THERMOSTAT_OUTPUT_REL5 ,
THERMOSTAT_OUTPUT_REL6 ,
THERMOSTAT_OUTPUT_REL7 ,
THERMOSTAT_OUTPUT_REL8
} ;
typedef union {
2020-04-29 20:42:20 +01:00
uint32_t data ;
2020-04-20 21:50:53 +01:00
struct {
2020-04-29 20:42:20 +01:00
uint32_t thermostat_mode : 2 ; // Operation mode of the thermostat system
uint32_t controller_mode : 2 ; // Operation mode of the thermostat controller
2020-05-05 22:56:44 +01:00
uint32_t climate_mode : 1 ; // Climate mode of the thermostat (0 = heating / 1 = cooling)
2020-04-29 20:42:20 +01:00
uint32_t sensor_alive : 1 ; // Flag stating if temperature sensor is alive (0 = inactive, 1 = active)
uint32_t sensor_type : 1 ; // Sensor type: MQTT/local
2020-05-05 22:56:44 +01:00
uint32_t temp_format : 1 ; // Temperature format (0 = Celsius, 1 = Fahrenheit)
2020-05-02 19:31:20 +01:00
uint32_t command_output : 1 ; // Flag stating the desired command to the output (0 = inactive, 1 = active)
uint32_t status_output : 1 ; // Flag stating state of the output (0 = inactive, 1 = active)
uint32_t status_input : 1 ; // Flag stating state of the input (0 = inactive, 1 = active)
2020-05-03 14:11:19 +01:00
uint32_t use_input : 1 ; // Flag stating if the input switch shall be used to switch to manual mode
2020-05-14 21:44:32 +01:00
uint32_t phase_hybrid_ctr : 2 ; // Phase of the hybrid controller (Ramp-up, PI or Autotune)
2020-04-29 20:42:20 +01:00
uint32_t status_cycle_active : 1 ; // Status showing if cycle is active (Output ON) or not (Output OFF)
uint32_t counter_seconds : 6 ; // Second counter used to track minutes
uint32_t output_relay_number : 4 ; // Output relay number
uint32_t input_switch_number : 3 ; // Input switch number
2020-05-20 21:10:49 +01:00
uint32_t enable_output : 1 ; // Enables / disables the physical output
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
uint32_t autotune_flag : 1 ; // Enable/disable autotune
uint32_t autotune_perf_mode : 2 ; // Autotune performance mode
# else
2020-05-20 21:10:49 +01:00
uint32_t free : 3 ; // Free bits
2020-05-14 21:44:32 +01:00
# endif // USE_PI_AUTOTUNING
} ;
} ThermostatStateBitfield ;
typedef union {
uint8_t data ;
struct {
uint8_t state_emergency : 1 ; // State for thermostat emergency
uint8_t diagnostic_mode : 1 ; // Diagnostic mode selected
uint8_t output_inconsist_ctr : 2 ; // Counter of the minutes where the output state is inconsistent with the command
2020-04-20 21:50:53 +01:00
} ;
2020-05-14 21:44:32 +01:00
} ThermostatDiagBitfield ;
2020-04-20 21:50:53 +01:00
# ifdef DEBUG_THERMOSTAT
const char DOMOTICZ_MES [ ] PROGMEM = " { \" idx \" :%d, \" nvalue \" :%d, \" svalue \" : \" %s \" } " ;
2020-05-03 14:11:19 +01:00
uint16_t Domoticz_Virtual_Switches [ DOMOTICZ_MAX_IDX ] = { DOMOTICZ_IDX1 , DOMOTICZ_IDX3 , DOMOTICZ_IDX4 , DOMOTICZ_IDX5 } ;
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
2020-10-10 14:19:11 +01:00
const char kThermostatCommands [ ] PROGMEM = " | " D_CMND_THERMOSTATMODESET " | " D_CMND_CLIMATEMODESET " | "
D_CMND_TEMPFROSTPROTECTSET " | " D_CMND_CONTROLLERMODESET " | " D_CMND_INPUTSWITCHSET " | " D_CMND_INPUTSWITCHUSE " | "
D_CMND_OUTPUTRELAYSET " | " D_CMND_TIMEALLOWRAMPUPSET " | " D_CMND_TEMPFORMATSET " | " D_CMND_TEMPMEASUREDSET " | "
D_CMND_TEMPTARGETSET " | " D_CMND_TEMPMEASUREDGRDREAD " | " D_CMND_SENSORINPUTSET " | " D_CMND_STATEEMERGENCYSET " | "
D_CMND_TIMEMANUALTOAUTOSET " | " D_CMND_PROPBANDSET " | " D_CMND_TIMERESETSET " | " D_CMND_TIMEPICYCLESET " | "
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
2020-10-10 14:19:11 +01:00
D_CMND_TEMPANTIWINDUPRESETSET " | " D_CMND_TEMPHYSTSET " | " D_CMND_PERFLEVELAUTOTUNE " | " D_CMND_TIMEMAXACTIONSET " | "
2020-05-14 21:44:32 +01:00
# else
2020-10-10 14:19:11 +01:00
D_CMND_TEMPANTIWINDUPRESETSET " | " D_CMND_TEMPHYSTSET " | " D_CMND_TIMEMAXACTIONSET " | "
2020-05-14 21:44:32 +01:00
# endif // USE_PI_AUTOTUNING
2020-10-10 14:19:11 +01:00
D_CMND_TIMEMINACTIONSET " | " D_CMND_TIMEMINTURNOFFACTIONSET " | " D_CMND_TEMPRUPDELTINSET " | " D_CMND_TEMPRUPDELTOUTSET " | "
D_CMND_TIMERAMPUPMAXSET " | " D_CMND_TIMERAMPUPCYCLESET " | " D_CMND_TEMPRAMPUPPIACCERRSET " | " D_CMND_TIMEPIPROPORTREAD " | "
D_CMND_TIMEPIINTEGRREAD " | " D_CMND_TIMESENSLOSTSET " | " D_CMND_DIAGNOSTICMODESET " | " D_CMND_CTRDUTYCYCLEREAD " | "
2020-05-20 21:10:49 +01:00
D_CMND_ENABLEOUTPUTSET ;
2020-04-20 21:50:53 +01:00
void ( * const ThermostatCommand [ ] ) ( void ) PROGMEM = {
2020-10-10 14:19:11 +01:00
& CmndThermostatModeSet , & CmndClimateModeSet , & CmndTempFrostProtectSet , & CmndControllerModeSet , & CmndInputSwitchSet ,
& CmndInputSwitchUse , & CmndOutputRelaySet , & CmndTimeAllowRampupSet , & CmndTempFormatSet , & CmndTempMeasuredSet ,
& CmndTempTargetSet , & CmndTempMeasuredGrdRead , & CmndSensorInputSet , & CmndStateEmergencySet , & CmndTimeManualToAutoSet ,
& CmndPropBandSet , & CmndTimeResetSet , & CmndTimePiCycleSet , & CmndTempAntiWindupResetSet , & CmndTempHystSet ,
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
2020-10-10 14:19:11 +01:00
& CmndPerfLevelAutotune , & CmndTimeMaxActionSet , & CmndTimeMinActionSet , & CmndTimeMinTurnoffActionSet , & CmndTempRupDeltInSet ,
2020-05-14 21:44:32 +01:00
# else
2020-10-10 14:19:11 +01:00
& CmndTimeMaxActionSet , & CmndTimeMinActionSet , & CmndTimeMinTurnoffActionSet , & CmndTempRupDeltInSet ,
2020-05-14 21:44:32 +01:00
# endif // USE_PI_AUTOTUNING
2020-10-10 14:19:11 +01:00
& CmndTempRupDeltOutSet , & CmndTimeRampupMaxSet , & CmndTimeRampupCycleSet , & CmndTempRampupPiAccErrSet ,
2020-05-20 21:10:49 +01:00
& CmndTimePiProportRead , & CmndTimePiIntegrRead , & CmndTimeSensLostSet , & CmndDiagnosticModeSet , & CmndCtrDutyCycleRead ,
& CmndEnableOutputSet } ;
2020-04-20 21:50:53 +01:00
struct THERMOSTAT {
2020-05-14 21:44:32 +01:00
ThermostatStateBitfield status ; // Bittfield including states as well as several flags
2020-04-20 23:14:39 +01:00
uint32_t timestamp_temp_measured_update = 0 ; // Timestamp of latest measurement update
uint32_t timestamp_temp_meas_change_update = 0 ; // Timestamp of latest measurement value change (> or < to previous)
uint32_t timestamp_output_off = 0 ; // Timestamp of latest thermostat output Off state
uint32_t timestamp_input_on = 0 ; // Timestamp of latest input On state
uint32_t time_thermostat_total = 0 ; // Time thermostat on within a specific timeframe
2020-05-14 21:44:32 +01:00
uint32_t time_ctr_checkpoint = 0 ; // Time to finalize the control cycle within the PI strategy or to switch to PI from Rampup in seconds
2020-04-26 07:36:15 +01:00
uint32_t time_ctr_changepoint = 0 ; // Time until switching off output within the controller in seconds
2020-04-20 23:14:39 +01:00
int32_t temp_measured_gradient = 0 ; // Temperature measured gradient from sensor in thousandths of degrees per hour
2020-04-29 20:42:20 +01:00
int16_t temp_target_level = THERMOSTAT_TEMP_INIT ; // Target level of the thermostat in tenths of degrees
int16_t temp_target_level_ctr = THERMOSTAT_TEMP_INIT ; // Target level set for the controller
2020-04-26 07:36:15 +01:00
int16_t temp_pi_accum_error = 0 ; // Temperature accumulated error for the PI controller in hundredths of degrees
int16_t temp_pi_error = 0 ; // Temperature error for the PI controller in hundredths of degrees
2020-04-20 23:14:39 +01:00
int32_t time_proportional_pi ; // Time proportional part of the PI controller
int32_t time_integral_pi ; // Time integral part of the PI controller
int32_t time_total_pi ; // Time total (proportional + integral) of the PI controller
2020-05-05 19:40:09 +01:00
uint16_t kP_pi = 0 ; // kP value for the PI controller multiplied by 100 (to avoid floating point operations)
2020-05-14 21:44:32 +01:00
uint16_t kI_pi = 0 ; // kI value for the PI controller multiplied by 100 (to avoid floating point operations)
2020-05-05 19:40:09 +01:00
int32_t temp_rampup_meas_gradient = 0 ; // Temperature measured gradient from sensor in thousandths of degrees celsius per hour calculated during ramp-up
2020-04-20 23:14:39 +01:00
uint32_t timestamp_rampup_start = 0 ; // Timestamp where the ramp-up controller mode has been started
uint32_t time_rampup_deadtime = 0 ; // Time constant of the thermostat system (step response time)
uint32_t time_rampup_nextcycle = 0 ; // Time where the ramp-up controller shall start the next cycle
2020-05-05 19:40:09 +01:00
int16_t temp_measured = 0 ; // Temperature measurement received from sensor in tenths of degrees celsius
int16_t temp_rampup_output_off = 0 ; // Temperature to swith off relay output within the ramp-up controller in tenths of degrees celsius
2020-04-23 21:41:20 +01:00
uint8_t time_output_delay = THERMOSTAT_TIME_OUTPUT_DELAY ; // Output delay between state change and real actuation event (f.i. valve open/closed)
2020-04-29 20:42:20 +01:00
uint8_t counter_rampup_cycles = 0 ; // Counter of ramp-up cycles
2020-05-05 19:40:09 +01:00
uint8_t temp_rampup_pi_acc_error = THERMOSTAT_TEMP_PI_RAMPUP_ACC_E ; // Accumulated error when switching from ramp-up controller to PI in hundreths of degrees celsius
2020-04-20 23:14:39 +01:00
uint8_t temp_rampup_delta_out = THERMOSTAT_TEMP_RAMPUP_DELTA_OUT ; // Minimum delta temperature to target to get out of the rampup mode, in tenths of degrees celsius
uint8_t temp_rampup_delta_in = THERMOSTAT_TEMP_RAMPUP_DELTA_IN ; // Minimum delta temperature to target to get into rampup mode, in tenths of degrees celsius
2020-04-29 20:42:20 +01:00
uint8_t val_prop_band = THERMOSTAT_PROP_BAND ; // Proportional band of the PI controller in degrees celsius
2020-04-20 23:14:39 +01:00
int16_t temp_rampup_start = 0 ; // Temperature at start of ramp-up controller in tenths of degrees celsius
int16_t temp_rampup_cycle = 0 ; // Temperature set at the beginning of each ramp-up cycle in tenths of degrees
uint16_t time_rampup_max = THERMOSTAT_TIME_RAMPUP_MAX ; // Time maximum ramp-up controller duration in minutes
2020-05-10 22:00:57 +01:00
uint16_t time_rampup_cycle = THERMOSTAT_TIME_RAMPUP_CYCLE ; // Time ramp-up cycle in minutes
2020-04-20 23:14:39 +01:00
uint16_t time_allow_rampup = THERMOSTAT_TIME_ALLOW_RAMPUP ; // Time in minutes after last target update to allow ramp-up controller phase
2020-05-10 22:00:57 +01:00
uint16_t time_sens_lost = THERMOSTAT_TIME_SENS_LOST ; // Maximum time w/o sensor update to set it as lost in minutes
2020-04-20 21:50:53 +01:00
uint16_t time_manual_to_auto = THERMOSTAT_TIME_MANUAL_TO_AUTO ; // Time without input switch active to change from manual to automatic in minutes
2020-04-29 20:42:20 +01:00
uint32_t time_reset = THERMOSTAT_TIME_RESET ; // Reset time of the PI controller in seconds
2020-05-10 22:00:57 +01:00
uint16_t time_pi_cycle = THERMOSTAT_TIME_PI_CYCLE ; // Cycle time for the thermostat controller in minutes
2020-04-20 21:50:53 +01:00
uint16_t time_max_action = THERMOSTAT_TIME_MAX_ACTION ; // Maximum thermostat time per cycle in minutes
uint16_t time_min_action = THERMOSTAT_TIME_MIN_ACTION ; // Minimum thermostat time per cycle in minutes
2020-05-10 22:00:57 +01:00
uint16_t time_min_turnoff_action = THERMOSTAT_TIME_MIN_TURNOFF_ACTION ; // Minimum turnoff time in minutes, below it the thermostat will stay on
2020-04-20 21:50:53 +01:00
uint8_t temp_reset_anti_windup = THERMOSTAT_TEMP_RESET_ANTI_WINDUP ; // Range where reset antiwindup is disabled, in tenths of degrees celsius
int8_t temp_hysteresis = THERMOSTAT_TEMP_HYSTERESIS ; // Range hysteresis for temperature PI controller, in tenths of degrees celsius
2020-04-29 20:42:20 +01:00
uint8_t temp_frost_protect = THERMOSTAT_TEMP_FROST_PROTECT ; // Minimum temperature for frost protection, in tenths of degrees celsius
2020-05-14 21:44:32 +01:00
ThermostatDiagBitfield diag ; // Bittfield including diagnostic flags
# ifdef USE_PI_AUTOTUNING
uint8_t dutycycle_step_autotune = THERMOSTAT_DUTYCYCLE_AUTOTUNE ; // Duty cycle for the step response of the autotune PI function in %
uint8_t peak_ctr = 0 ; // Peak counter for the autotuning function
uint8_t temp_band_no_peak_det = THERMOSTAT_TEMP_BAND_NO_PEAK_DET ; // Temperature band in thenths of degrees celsius within no peak will be detected
uint8_t val_prop_band_atune = 0 ; // Proportional band calculated from the the PI autotune function in degrees celsius
uint32_t time_reset_atune = 0 ; // Reset time calculated from the PI autotune function in seconds
uint16_t pU_pi_atune = 0 ; // pU value ("Ultimate" period) period of self-sustaining oscillations determined when the controller gain was set to Ku in minutes (for PI autotune)
uint16_t kU_pi_atune = 0 ; // kU value ("Ultimate" gain) determined by increasing controller gain until self-sustaining oscillations are achieved (for PI autotune)
uint16_t kP_pi_atune = 0 ; // kP value calculated by the autotune PI function multiplied by 100 (to avoid floating point operations)
uint16_t kI_pi_atune = 0 ; // kI value calulated by the autotune PI function multiplied by 100 (to avoid floating point operations)
int16_t temp_peaks_atune [ THERMOSTAT_PEAKNUMBER_AUTOTUNE ] ; // Array to store temperature peaks to be used by the autotune PI function
int16_t temp_abs_max_atune ; // Max temperature reached within autotune
int16_t temp_abs_min_atune ; // Min temperature reached within autotune
uint16_t time_peak_timestamps_atune [ THERMOSTAT_PEAKNUMBER_AUTOTUNE ] ; // Array to store timestamps in minutes of the temperature peaks to be used by the autotune PI function
uint16_t time_std_dev_peak_det_ok = THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK ; // Standard deviation in minutes of the oscillation periods within the peak detection is successful
# endif // USE_PI_AUTOTUNING
2020-05-02 21:33:29 +01:00
} Thermostat [ THERMOSTAT_CONTROLLER_OUTPUTS ] ;
2020-04-20 21:50:53 +01:00
/*********************************************************************************************/
2020-05-02 21:33:29 +01:00
void ThermostatInit ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 21:33:29 +01:00
// Init Thermostat[ctr_output].status bitfield:
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_OFF ;
Thermostat [ ctr_output ] . status . controller_mode = CTR_HYBRID ;
2020-05-05 19:40:09 +01:00
Thermostat [ ctr_output ] . status . climate_mode = CLIMATE_HEATING ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . sensor_alive = IFACE_OFF ;
Thermostat [ ctr_output ] . status . sensor_type = SENSOR_MQTT ;
Thermostat [ ctr_output ] . status . temp_format = TEMP_CELSIUS ;
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
Thermostat [ ctr_output ] . status . status_output = IFACE_OFF ;
Thermostat [ ctr_output ] . status . phase_hybrid_ctr = CTR_HYBRID_PI ;
Thermostat [ ctr_output ] . status . status_cycle_active = CYCLE_OFF ;
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . diag . state_emergency = EMERGENCY_OFF ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . counter_seconds = 0 ;
Thermostat [ ctr_output ] . status . output_relay_number = ( THERMOSTAT_RELAY_NUMBER + ctr_output ) ;
Thermostat [ ctr_output ] . status . input_switch_number = ( THERMOSTAT_SWITCH_NUMBER + ctr_output ) ;
2020-05-03 14:11:19 +01:00
Thermostat [ ctr_output ] . status . use_input = INPUT_NOT_USED ;
2020-05-20 21:10:49 +01:00
Thermostat [ ctr_output ] . status . enable_output = IFACE_ON ;
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . diag . output_inconsist_ctr = 0 ;
Thermostat [ ctr_output ] . diag . diagnostic_mode = DIAGNOSTIC_ON ;
# ifdef USE_PI_AUTOTUNING
Thermostat [ ctr_output ] . status . autotune_flag = AUTOTUNE_OFF ;
Thermostat [ ctr_output ] . status . autotune_perf_mode = AUTOTUNE_PERF_FAST ;
# endif // USE_PI_AUTOTUNING
2020-04-29 20:42:20 +01:00
// Make sure the Output is OFF
2020-05-20 21:10:49 +01:00
if ( Thermostat [ ctr_output ] . status . enable_output = = IFACE_ON ) {
ExecuteCommandPower ( Thermostat [ ctr_output ] . status . output_relay_number , POWER_OFF , SRC_THERMOSTAT ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-10-10 14:19:11 +01:00
bool ThermostatMinuteCounter ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
bool result = false ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . counter_seconds + + ; // increment time
2020-10-10 14:19:11 +01:00
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . status . counter_seconds % 60 ) = = 0 ) {
2020-10-10 14:19:11 +01:00
result = true ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . counter_seconds = 0 ;
2020-04-20 21:50:53 +01:00
}
2020-04-24 22:18:17 +01:00
return result ;
2020-04-20 21:50:53 +01:00
}
2020-10-10 14:19:11 +01:00
inline bool ThermostatSwitchIdValid ( uint8_t switchId )
2020-04-20 21:50:53 +01:00
{
2020-05-20 21:10:49 +01:00
return ( switchId > = THERMOSTAT_INPUT_SWT1 & & switchId < = THERMOSTAT_INPUT_SWT8 ) ;
2020-04-20 21:50:53 +01:00
}
2020-10-10 14:19:11 +01:00
inline bool ThermostatRelayIdValid ( uint8_t relayId )
2020-04-20 21:50:53 +01:00
{
return ( relayId > = THERMOSTAT_OUTPUT_REL1 & & relayId < = THERMOSTAT_OUTPUT_REL8 ) ;
}
2020-05-02 19:31:20 +01:00
uint8_t ThermostatInputStatus ( uint8_t input_switch )
2020-04-20 21:50:53 +01:00
{
bool ifId = ThermostatSwitchIdValid ( input_switch ) ;
2020-05-02 19:31:20 +01:00
uint8_t value = 0 ;
2020-04-20 21:50:53 +01:00
if ( ifId ) {
2020-05-02 19:31:20 +01:00
value = SwitchGetVirtual ( ifId - THERMOSTAT_INPUT_SWT1 ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 19:31:20 +01:00
return value ;
}
uint8_t ThermostatOutputStatus ( uint8_t output_switch )
{
2020-10-28 18:03:39 +00:00
return ( uint8_t ) bitRead ( TasmotaGlobal . power , ( output_switch - 1 ) ) ;
2020-04-20 21:50:53 +01:00
}
2020-10-10 14:19:11 +01:00
int16_t ThermostatCelsiusToFahrenheit ( const int32_t deg , uint8_t conv_type ) {
2020-05-01 20:28:58 +01:00
int32_t value ;
value = ( int32_t ) ( ( ( int32_t ) deg * ( int32_t ) 90 ) / ( int32_t ) 50 ) ;
if ( conv_type = = TEMP_CONV_ABSOLUTE ) {
value + = ( int32_t ) 320 ;
}
// Protect overflow
if ( value < = ( int32_t ) ( INT16_MIN ) ) {
value = ( int32_t ) ( INT16_MIN ) ;
}
else if ( value > = ( int32_t ) INT16_MAX ) {
value = ( int32_t ) INT16_MAX ;
}
return ( int16_t ) value ;
}
2020-10-10 14:19:11 +01:00
int16_t ThermostatFahrenheitToCelsius ( const int32_t deg , uint8_t conv_type ) {
2020-05-01 20:28:58 +01:00
int16_t offset = 0 ;
int32_t value ;
if ( conv_type = = TEMP_CONV_ABSOLUTE ) {
offset = 320 ;
}
value = ( int32_t ) ( ( ( deg - ( int32_t ) offset ) * ( int32_t ) 50 ) / ( int32_t ) 90 ) ;
// Protect overflow
if ( value < = ( int32_t ) ( INT16_MIN ) ) {
value = ( int32_t ) ( INT16_MIN ) ;
}
else if ( value > = ( int32_t ) INT16_MAX ) {
value = ( int32_t ) INT16_MAX ;
}
return ( int16_t ) value ;
}
2020-05-02 21:33:29 +01:00
void ThermostatSignalPreProcessingSlow ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 19:31:20 +01:00
// Update input sensor status
2020-10-28 16:32:07 +00:00
if ( ( TasmotaGlobal . uptime - Thermostat [ ctr_output ] . timestamp_temp_measured_update ) > ( ( uint32_t ) Thermostat [ ctr_output ] . time_sens_lost * 60 ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . sensor_alive = IFACE_OFF ;
Thermostat [ ctr_output ] . temp_measured_gradient = 0 ;
Thermostat [ ctr_output ] . temp_measured = 0 ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-02 21:33:29 +01:00
void ThermostatSignalPostProcessingSlow ( uint8_t ctr_output )
2020-05-02 19:31:20 +01:00
{
// Increate counter when inconsistent output state exists
2020-05-20 21:10:49 +01:00
if ( ( Thermostat [ ctr_output ] . status . status_output ! = Thermostat [ ctr_output ] . status . command_output )
& & ( Thermostat [ ctr_output ] . status . enable_output = = IFACE_ON ) ) {
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . diag . output_inconsist_ctr + + ;
2020-05-02 19:31:20 +01:00
}
else {
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . diag . output_inconsist_ctr = 0 ;
2020-05-02 19:31:20 +01:00
}
}
2020-05-02 21:33:29 +01:00
void ThermostatSignalProcessingFast ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 19:31:20 +01:00
// Update real status of the input
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . status_input = ( uint32_t ) ThermostatInputStatus ( Thermostat [ ctr_output ] . status . input_switch_number ) ;
2020-05-02 19:31:20 +01:00
// Update timestamp of last input
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . status_input = = IFACE_ON ) {
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . timestamp_input_on = TasmotaGlobal . uptime ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 19:31:20 +01:00
// Update real status of the output
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . status_output = ( uint32_t ) ThermostatOutputStatus ( Thermostat [ ctr_output ] . status . output_relay_number ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
void ThermostatCtrState ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
bool flag_heating = ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_HEATING ) ;
# endif //USE_PI_AUTOTUNING
2020-05-02 21:33:29 +01:00
switch ( Thermostat [ ctr_output ] . status . controller_mode ) {
2020-05-02 19:31:20 +01:00
// Hybrid controller (Ramp-up + PI)
case CTR_HYBRID :
2020-05-02 21:33:29 +01:00
ThermostatHybridCtrPhase ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
break ;
2020-05-02 19:31:20 +01:00
// PI controller
case CTR_PI :
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
// If Autotune has been enabled (via flag)
// AND we have just reached the setpoint temperature
// AND the temperature gradient is negative for heating and positive for cooling
// then switch state to PI autotuning
if ( ( Thermostat [ ctr_output ] . status . autotune_flag = = AUTOTUNE_ON )
& & ( Thermostat [ ctr_output ] . temp_measured = = Thermostat [ ctr_output ] . temp_target_level )
& & ( ( flag_heating & & ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 ) )
| | ( ! flag_heating & & ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 ) ) ) )
{
Thermostat [ ctr_output ] . status . controller_mode = CTR_PI_AUTOTUNE ;
ThermostatPeakDetectorInit ( ctr_output ) ;
}
# endif // USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
break ;
2020-05-02 19:31:20 +01:00
// Ramp-up controller (predictive)
case CTR_RAMP_UP :
2020-04-20 21:50:53 +01:00
break ;
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_PI_AUTOTUNE :
// If autotune finalized (flag Off)
// then go back to the PI controller
if ( Thermostat [ ctr_output ] . status . autotune_flag = = AUTOTUNE_OFF )
{
Thermostat [ ctr_output ] . status . controller_mode = CTR_PI ;
}
break ;
# endif //USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
}
}
2020-05-02 21:33:29 +01:00
void ThermostatHybridCtrPhase ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-05 20:11:32 +01:00
bool flag_heating = ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_HEATING ) ;
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . controller_mode = = CTR_HYBRID ) {
switch ( Thermostat [ ctr_output ] . status . phase_hybrid_ctr ) {
2020-05-02 19:31:20 +01:00
// Ramp-up phase with gradient control
case CTR_HYBRID_RAMP_UP :
2020-10-10 14:19:11 +01:00
// If ramp-up offtime counter has been initalized
2020-04-20 21:50:53 +01:00
// AND ramp-up offtime counter value reached
2020-10-10 14:19:11 +01:00
if ( ( Thermostat [ ctr_output ] . time_ctr_checkpoint ! = 0 )
2020-10-28 16:32:07 +00:00
& & ( TasmotaGlobal . uptime > = Thermostat [ ctr_output ] . time_ctr_checkpoint ) ) {
2020-04-20 21:50:53 +01:00
// Reset pause period
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = 0 ;
2020-04-20 21:50:53 +01:00
// Reset timers
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_changepoint = 0 ;
2020-04-20 21:50:53 +01:00
// Set PI controller
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . phase_hybrid_ctr = CTR_HYBRID_PI ;
2020-04-20 21:50:53 +01:00
}
break ;
2020-05-02 19:31:20 +01:00
// PI controller phase
case CTR_HYBRID_PI :
2020-04-20 21:50:53 +01:00
// If no output action for a pre-defined time
// AND temp target has changed
2020-05-05 19:40:09 +01:00
// AND value of temp target - actual temperature bigger than threshold for heating and lower for cooling
2020-04-20 21:50:53 +01:00
// then go to ramp-up
2020-10-28 16:32:07 +00:00
if ( ( ( TasmotaGlobal . uptime - Thermostat [ ctr_output ] . timestamp_output_off ) > ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_allow_rampup ) )
2020-05-02 21:33:29 +01:00
& & ( Thermostat [ ctr_output ] . temp_target_level ! = Thermostat [ ctr_output ] . temp_target_level_ctr )
2020-05-05 19:40:09 +01:00
& & ( ( ( Thermostat [ ctr_output ] . temp_target_level - Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_rampup_delta_in )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured - Thermostat [ ctr_output ] . temp_target_level > Thermostat [ ctr_output ] . temp_rampup_delta_in )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) ) {
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . timestamp_rampup_start = TasmotaGlobal . uptime ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_start = Thermostat [ ctr_output ] . temp_measured ;
Thermostat [ ctr_output ] . temp_rampup_meas_gradient = 0 ;
Thermostat [ ctr_output ] . time_rampup_deadtime = 0 ;
Thermostat [ ctr_output ] . counter_rampup_cycles = 1 ;
Thermostat [ ctr_output ] . time_ctr_changepoint = 0 ;
Thermostat [ ctr_output ] . time_ctr_checkpoint = 0 ;
Thermostat [ ctr_output ] . status . phase_hybrid_ctr = CTR_HYBRID_RAMP_UP ;
2020-04-20 21:50:53 +01:00
}
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
// If Autotune has been enabled (via flag)
// AND we have just reached the setpoint temperature
// AND the temperature gradient is negative for heating and positive for cooling
// then switch state to PI autotuning
if ( ( Thermostat [ ctr_output ] . status . autotune_flag = = AUTOTUNE_ON )
& & ( Thermostat [ ctr_output ] . temp_measured = = Thermostat [ ctr_output ] . temp_target_level )
& & ( ( flag_heating & & ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 ) )
| | ( ! flag_heating & & ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 ) ) ) )
{
Thermostat [ ctr_output ] . status . phase_hybrid_ctr = CTR_HYBRID_PI_AUTOTUNE ;
ThermostatPeakDetectorInit ( ctr_output ) ;
}
# endif // USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
break ;
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
// PI autotune controller phase
case CTR_HYBRID_PI_AUTOTUNE :
// If autotune finalized (flag Off)
// then go back to the PI controller
if ( Thermostat [ ctr_output ] . status . autotune_flag = = AUTOTUNE_OFF )
{
Thermostat [ ctr_output ] . status . phase_hybrid_ctr = CTR_HYBRID_PI ;
}
2020-10-10 14:19:11 +01:00
break ;
2020-05-14 21:44:32 +01:00
# endif // USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
}
}
# ifdef DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
ThermostatVirtualSwitchCtrState ( ctr_output ) ;
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
bool ThermostatStateAutoToManual ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
bool change_state = false ;
2020-05-03 14:11:19 +01:00
// If input is used
// AND switch input is active
// OR temperature sensor is not alive
2020-04-20 21:50:53 +01:00
// then go to manual
2020-05-03 14:11:19 +01:00
if ( ( Thermostat [ ctr_output ] . status . use_input = = INPUT_USED )
& & ( ( Thermostat [ ctr_output ] . status . status_input = = IFACE_ON )
| | ( Thermostat [ ctr_output ] . status . sensor_alive = = IFACE_OFF ) ) ) {
2020-04-20 21:50:53 +01:00
change_state = true ;
}
return change_state ;
}
2020-05-02 21:33:29 +01:00
bool ThermostatStateManualToAuto ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-04-29 12:46:22 +01:00
bool change_state = false ;
2020-04-20 21:50:53 +01:00
// If switch input inactive
2020-04-23 21:39:28 +01:00
// AND sensor alive
// AND no switch input action (time in current state) bigger than a pre-defined time
// then go to automatic
2020-10-10 14:19:11 +01:00
if ( ( Thermostat [ ctr_output ] . status . status_input = = IFACE_OFF )
2020-05-02 21:33:29 +01:00
& & ( Thermostat [ ctr_output ] . status . sensor_alive = = IFACE_ON )
2020-10-28 16:32:07 +00:00
& & ( ( TasmotaGlobal . uptime - Thermostat [ ctr_output ] . timestamp_input_on ) > ( ( uint32_t ) Thermostat [ ctr_output ] . time_manual_to_auto * 60 ) ) ) {
2020-04-20 21:50:53 +01:00
change_state = true ;
}
return change_state ;
}
2020-05-02 21:33:29 +01:00
void ThermostatEmergencyShutdown ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 19:31:20 +01:00
// Emergency switch to THERMOSTAT_OFF
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_OFF ;
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
2020-05-20 21:10:49 +01:00
if ( Thermostat [ ctr_output ] . status . enable_output = = IFACE_ON ) {
ThermostatOutputRelay ( ctr_output , Thermostat [ ctr_output ] . status . command_output ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
void ThermostatState ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 21:33:29 +01:00
switch ( Thermostat [ ctr_output ] . status . thermostat_mode ) {
2020-05-02 19:31:20 +01:00
// State if Off or Emergency
case THERMOSTAT_OFF :
2020-04-20 21:50:53 +01:00
// No change of state possible without external command
break ;
2020-05-05 19:40:09 +01:00
// State automatic, thermostat active following the command target temp.
2020-05-02 19:31:20 +01:00
case THERMOSTAT_AUTOMATIC_OP :
2020-05-02 21:33:29 +01:00
if ( ThermostatStateAutoToManual ( ctr_output ) ) {
2020-05-02 19:31:20 +01:00
// If sensor not alive change to THERMOSTAT_MANUAL_OP
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_MANUAL_OP ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ThermostatCtrState ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
break ;
2020-05-02 19:31:20 +01:00
// State manual operation following input switch
case THERMOSTAT_MANUAL_OP :
2020-05-02 21:33:29 +01:00
if ( ThermostatStateManualToAuto ( ctr_output ) ) {
2020-05-02 19:31:20 +01:00
// Input switch inactive and timeout reached change to THERMOSTAT_AUTOMATIC_OP
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_AUTOMATIC_OP ;
2020-04-20 21:50:53 +01:00
}
break ;
}
}
2020-05-02 21:33:29 +01:00
void ThermostatOutputRelay ( uint8_t ctr_output , uint32_t command )
2020-04-20 21:50:53 +01:00
{
// If command received to enable output
// AND current output status is OFF
// then switch output to ON
2020-10-10 14:19:11 +01:00
if ( ( command = = IFACE_ON )
2020-05-02 21:33:29 +01:00
& & ( Thermostat [ ctr_output ] . status . status_output = = IFACE_OFF ) ) {
2020-05-02 19:31:20 +01:00
//#ifndef DEBUG_THERMOSTAT
2020-05-20 21:10:49 +01:00
if ( Thermostat [ ctr_output ] . status . enable_output = = IFACE_ON ) {
ExecuteCommandPower ( Thermostat [ ctr_output ] . status . output_relay_number , POWER_ON , SRC_THERMOSTAT ) ;
}
2020-05-02 19:31:20 +01:00
//#endif // DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . status_output = IFACE_ON ;
2020-04-20 21:50:53 +01:00
# ifdef DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
ThermostatVirtualSwitch ( ctr_output ) ;
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
}
// If command received to disable output
// AND current output status is ON
// then switch output to OFF
2020-05-02 21:33:29 +01:00
else if ( ( command = = IFACE_OFF ) & & ( Thermostat [ ctr_output ] . status . status_output = = IFACE_ON ) ) {
2020-05-02 19:31:20 +01:00
//#ifndef DEBUG_THERMOSTAT
2020-05-20 21:10:49 +01:00
if ( Thermostat [ ctr_output ] . status . enable_output = = IFACE_ON ) {
ExecuteCommandPower ( Thermostat [ ctr_output ] . status . output_relay_number , POWER_OFF , SRC_THERMOSTAT ) ;
}
2020-05-02 19:31:20 +01:00
//#endif // DEBUG_THERMOSTAT
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . timestamp_output_off = TasmotaGlobal . uptime ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . status_output = IFACE_OFF ;
2020-04-20 21:50:53 +01:00
# ifdef DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
ThermostatVirtualSwitch ( ctr_output ) ;
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
}
}
2020-05-02 21:33:29 +01:00
void ThermostatCalculatePI ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-01 20:28:58 +01:00
// General comment: Some variables have been increased in resolution to avoid loosing accuracy in division operations
2020-10-10 14:19:11 +01:00
2020-05-05 20:11:32 +01:00
bool flag_heating = ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_HEATING ) ;
2020-05-05 19:40:09 +01:00
int32_t aux_temp_error ;
2020-10-10 14:19:11 +01:00
2020-04-20 21:50:53 +01:00
// Calculate error
2020-05-05 19:40:09 +01:00
aux_temp_error = ( int32_t ) ( Thermostat [ ctr_output ] . temp_target_level_ctr - Thermostat [ ctr_output ] . temp_measured ) * 10 ;
2020-10-10 14:19:11 +01:00
2020-05-05 19:40:09 +01:00
// Invert error for cooling
if ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_COOLING ) {
aux_temp_error * = - 1 ;
}
2020-04-26 16:59:36 +01:00
// Protect overflow
2020-05-05 19:40:09 +01:00
if ( aux_temp_error < = ( int32_t ) ( INT16_MIN ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_error = ( int16_t ) ( INT16_MIN ) ;
2020-04-26 16:59:36 +01:00
}
2020-05-05 19:40:09 +01:00
else if ( aux_temp_error > = ( int32_t ) INT16_MAX ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_error = ( int16_t ) INT16_MAX ;
2020-04-26 16:59:36 +01:00
}
else {
2020-05-05 19:40:09 +01:00
Thermostat [ ctr_output ] . temp_pi_error = ( int16_t ) aux_temp_error ;
2020-04-26 16:59:36 +01:00
}
2020-10-10 14:19:11 +01:00
2020-04-20 21:50:53 +01:00
// Kp = 100/PI.propBand. PI.propBand(Xp) = Proportional range (4K in 4K/200 controller)
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . kP_pi = 100 / ( uint16_t ) ( Thermostat [ ctr_output ] . val_prop_band ) ;
2020-04-20 21:50:53 +01:00
// Calculate proportional
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_proportional_pi = ( ( int32_t ) ( Thermostat [ ctr_output ] . temp_pi_error * ( int16_t ) Thermostat [ ctr_output ] . kP_pi ) * ( ( int32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ) / 10000 ;
2020-04-20 21:50:53 +01:00
// Minimum proportional action limiter
// If proportional action is less than the minimum action time
// AND proportional > 0
// then adjust to minimum value
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . time_proportional_pi < abs ( ( ( int32_t ) Thermostat [ ctr_output ] . time_min_action * 60 ) ) )
& & ( Thermostat [ ctr_output ] . time_proportional_pi > 0 ) ) {
Thermostat [ ctr_output ] . time_proportional_pi = ( ( int32_t ) Thermostat [ ctr_output ] . time_min_action * 60 ) ;
2020-04-20 21:50:53 +01:00
}
2020-10-10 14:19:11 +01:00
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . time_proportional_pi < 0 ) {
Thermostat [ ctr_output ] . time_proportional_pi = 0 ;
2020-10-10 14:19:11 +01:00
}
2020-05-02 21:33:29 +01:00
else if ( Thermostat [ ctr_output ] . time_proportional_pi > ( ( int32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ) {
Thermostat [ ctr_output ] . time_proportional_pi = ( ( int32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ;
2020-04-20 21:50:53 +01:00
}
2020-04-26 16:59:36 +01:00
// Calculate integral (resolution increased to avoid use of floats in consequent operations)
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . kI_pi = ( uint16_t ) ( ( ( ( uint32_t ) Thermostat [ ctr_output ] . kP_pi * ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 6000 ) ) / ( uint32_t ) Thermostat [ ctr_output ] . time_reset ) ;
2020-10-10 14:19:11 +01:00
2020-04-20 21:50:53 +01:00
// Reset of antiwindup
// If error does not lay within the integrator scope range, do not use the integral
// and accumulate error = 0
2020-05-02 21:33:29 +01:00
if ( abs ( ( Thermostat [ ctr_output ] . temp_pi_error ) / 10 ) > Thermostat [ ctr_output ] . temp_reset_anti_windup ) {
Thermostat [ ctr_output ] . time_integral_pi = 0 ;
Thermostat [ ctr_output ] . temp_pi_accum_error = 0 ;
2020-04-20 21:50:53 +01:00
}
// Normal use of integrator
2020-10-10 14:19:11 +01:00
// result will be calculated with the cummulated previous error anterior
2020-04-20 21:50:53 +01:00
// and current error will be cummulated to the previous one
else {
// Hysteresis limiter
// If error is less than or equal than hysteresis, limit output to 0, when temperature
// is rising, never when falling. Limit cummulated error. If this is not done,
2020-10-10 14:19:11 +01:00
// there will be very strong control actions from the integral part due to a
2020-04-20 21:50:53 +01:00
// very high cummulated error when beingin hysteresis. This triggers high
// integral actions
2020-04-26 16:59:36 +01:00
// Update accumulated error
2020-05-05 19:40:09 +01:00
aux_temp_error = ( int32_t ) Thermostat [ ctr_output ] . temp_pi_accum_error + ( int32_t ) Thermostat [ ctr_output ] . temp_pi_error ;
2020-04-26 16:59:36 +01:00
// Protect overflow
2020-05-05 19:40:09 +01:00
if ( aux_temp_error < = ( int32_t ) INT16_MIN ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_accum_error = INT16_MIN ;
2020-04-26 16:59:36 +01:00
}
2020-05-05 19:40:09 +01:00
else if ( aux_temp_error > = ( int32_t ) INT16_MAX ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_accum_error = INT16_MAX ;
2020-04-26 16:59:36 +01:00
}
else {
2020-05-05 19:40:09 +01:00
Thermostat [ ctr_output ] . temp_pi_accum_error = ( int16_t ) aux_temp_error ;
2020-04-26 16:59:36 +01:00
}
2020-04-20 21:50:53 +01:00
// If we are under setpoint
// AND we are within the hysteresis
2020-05-05 19:40:09 +01:00
// AND the temperature is rising for heating or sinking for cooling
if ( ( Thermostat [ ctr_output ] . temp_pi_error > = 0 )
2020-05-02 21:33:29 +01:00
& & ( abs ( ( Thermostat [ ctr_output ] . temp_pi_error ) / 10 ) < = ( int16_t ) Thermostat [ ctr_output ] . temp_hysteresis )
2020-05-05 19:40:09 +01:00
& & ( ( ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) ) {
2020-04-20 21:50:53 +01:00
// Reduce accumulator error 20% in each cycle
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_accum_error * = 0.8 ;
2020-04-20 21:50:53 +01:00
}
// If we are over setpoint
2020-05-05 19:40:09 +01:00
// AND temperature is rising for heating or sinking for cooling
2020-05-02 21:33:29 +01:00
else if ( ( Thermostat [ ctr_output ] . temp_pi_error < 0 )
2020-05-05 19:40:09 +01:00
& & ( ( ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) ) {
2020-04-20 21:50:53 +01:00
// Reduce accumulator error 20% in each cycle
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_accum_error * = 0.8 ;
2020-04-20 21:50:53 +01:00
}
// Limit lower limit of acumErr to 0
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . temp_pi_accum_error < 0 ) {
Thermostat [ ctr_output ] . temp_pi_accum_error = 0 ;
2020-04-20 21:50:53 +01:00
}
// Integral calculation
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_integral_pi = ( ( ( int32_t ) Thermostat [ ctr_output ] . temp_pi_accum_error * ( int32_t ) Thermostat [ ctr_output ] . kI_pi ) * ( int32_t ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ) / 1000000 ;
2020-04-20 21:50:53 +01:00
// Antiwindup of the integrator
// If integral calculation is bigger than cycle time, adjust result
2020-05-10 22:00:57 +01:00
// to the cycle time and error will not be cummulated
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . time_integral_pi > ( ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ) {
Thermostat [ ctr_output ] . time_integral_pi = ( ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ;
2020-04-20 21:50:53 +01:00
}
}
// Calculate output
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_total_pi = Thermostat [ ctr_output ] . time_proportional_pi + Thermostat [ ctr_output ] . time_integral_pi ;
2020-10-10 14:19:11 +01:00
2020-05-02 21:34:08 +01:00
// Antiwindup of the output
// If result is bigger than cycle time, the result will be adjusted
2020-05-10 22:00:57 +01:00
// to the cylce time minus safety time and error will not be cummulated
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . time_total_pi > = ( ( int32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ) {
2020-04-20 21:50:53 +01:00
// Limit to cycle time //at least switch down a minimum time
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_total_pi = ( ( int32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
else if ( Thermostat [ ctr_output ] . time_total_pi < 0 ) {
Thermostat [ ctr_output ] . time_total_pi = 0 ;
2020-04-20 21:50:53 +01:00
}
// Target value limiter
2020-05-05 19:40:09 +01:00
// If target value has been reached or we are over it for heating or under it for cooling
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . temp_pi_error < = 0 ) {
2020-05-05 19:40:09 +01:00
// If we are over the hysteresis or the gradient is positive for heating or negative for cooling
2020-05-02 21:33:29 +01:00
if ( ( abs ( ( Thermostat [ ctr_output ] . temp_pi_error ) / 10 ) > Thermostat [ ctr_output ] . temp_hysteresis )
2020-05-05 19:40:09 +01:00
| | ( ( ( Thermostat [ ctr_output ] . temp_measured_gradient > = 0 )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured_gradient < = 0 )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_total_pi = 0 ;
2020-04-20 21:50:53 +01:00
}
2020-10-10 14:19:11 +01:00
}
2020-04-20 21:50:53 +01:00
// If target value has not been reached
2020-05-05 19:40:09 +01:00
// AND we are within the histeresis
// AND gradient is positive for heating or negative for cooling
2020-04-20 21:50:53 +01:00
// then set value to 0
2020-05-02 21:33:29 +01:00
else if ( ( Thermostat [ ctr_output ] . temp_pi_error > 0 )
& & ( abs ( ( Thermostat [ ctr_output ] . temp_pi_error ) / 10 ) < = Thermostat [ ctr_output ] . temp_hysteresis )
2020-05-05 19:40:09 +01:00
& & ( ( ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_total_pi = 0 ;
2020-04-20 21:50:53 +01:00
}
// Minimum action limiter
2020-05-10 22:00:57 +01:00
// If result is less than the minimum action time, adjust to minimum value
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . time_total_pi < = abs ( ( ( uint32_t ) Thermostat [ ctr_output ] . time_min_action * 60 ) ) )
& & ( Thermostat [ ctr_output ] . time_total_pi ! = 0 ) ) {
Thermostat [ ctr_output ] . time_total_pi = ( ( int32_t ) Thermostat [ ctr_output ] . time_min_action * 60 ) ;
2020-04-20 21:50:53 +01:00
}
// Maximum action limiter
2020-05-10 22:00:57 +01:00
// If result is more than the maximum action time, adjust to maximum value
2020-05-02 21:33:29 +01:00
else if ( Thermostat [ ctr_output ] . time_total_pi > abs ( ( ( int32_t ) Thermostat [ ctr_output ] . time_max_action * 60 ) ) ) {
Thermostat [ ctr_output ] . time_total_pi = ( ( int32_t ) Thermostat [ ctr_output ] . time_max_action * 60 ) ;
2020-04-20 21:50:53 +01:00
}
// If switched off less time than safety time, do not switch off
2020-05-02 21:33:29 +01:00
else if ( Thermostat [ ctr_output ] . time_total_pi > ( ( ( int32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) - ( ( int32_t ) Thermostat [ ctr_output ] . time_min_turnoff_action * 60 ) ) ) {
Thermostat [ ctr_output ] . time_total_pi = ( ( int32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ;
2020-04-20 21:50:53 +01:00
}
2020-10-10 14:19:11 +01:00
2020-04-20 21:50:53 +01:00
// Adjust output switch point
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_changepoint = TasmotaGlobal . uptime + ( uint32_t ) Thermostat [ ctr_output ] . time_total_pi ;
2020-04-20 21:50:53 +01:00
// Adjust next cycle point
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = TasmotaGlobal . uptime + ( ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
void ThermostatWorkAutomaticPI ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-05 20:11:32 +01:00
bool flag_heating = ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_HEATING ) ;
2020-10-28 16:32:07 +00:00
if ( ( TasmotaGlobal . uptime > = Thermostat [ ctr_output ] . time_ctr_checkpoint )
2020-05-02 21:33:29 +01:00
| | ( Thermostat [ ctr_output ] . temp_target_level ! = Thermostat [ ctr_output ] . temp_target_level_ctr )
2020-05-05 19:40:09 +01:00
| | ( ( ( ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_target_level )
& & ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_target_level )
& & ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) )
2020-05-02 21:33:29 +01:00
& & ( Thermostat [ ctr_output ] . status . status_cycle_active = = CYCLE_OFF ) ) ) {
Thermostat [ ctr_output ] . temp_target_level_ctr = Thermostat [ ctr_output ] . temp_target_level ;
ThermostatCalculatePI ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
// Reset cycle active
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . status_cycle_active = CYCLE_OFF ;
2020-04-20 21:50:53 +01:00
}
2020-10-28 16:32:07 +00:00
if ( TasmotaGlobal . uptime < Thermostat [ ctr_output ] . time_ctr_changepoint ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . status_cycle_active = CYCLE_ON ;
Thermostat [ ctr_output ] . status . command_output = IFACE_ON ;
2020-04-20 21:50:53 +01:00
}
else {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-02 21:33:29 +01:00
void ThermostatWorkAutomaticRampUp ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
uint32_t time_in_rampup ;
2020-05-05 20:11:32 +01:00
int16_t aux_temp_delta ;
2020-04-20 21:50:53 +01:00
int16_t temp_delta_rampup ;
2020-05-05 20:11:32 +01:00
bool flag_heating = ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_HEATING ) ;
2020-04-20 21:50:53 +01:00
2020-10-10 14:19:11 +01:00
// Update timestamp for temperature at start of ramp-up if temperature still
2020-05-05 19:40:09 +01:00
// dropping for heating or rising for cooling
if ( ( ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_rampup_start )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_rampup_start )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) )
2020-05-05 19:40:09 +01:00
{
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_start = Thermostat [ ctr_output ] . temp_measured ;
2020-04-20 21:50:53 +01:00
}
// Update time in ramp-up as well as delta temp
2020-10-28 16:32:07 +00:00
time_in_rampup = TasmotaGlobal . uptime - Thermostat [ ctr_output ] . timestamp_rampup_start ;
2020-05-02 21:33:29 +01:00
temp_delta_rampup = Thermostat [ ctr_output ] . temp_measured - Thermostat [ ctr_output ] . temp_rampup_start ;
2020-04-20 21:50:53 +01:00
// Init command output status to true
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . command_output = IFACE_ON ;
2020-04-20 21:50:53 +01:00
// Update temperature target level for controller
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_target_level_ctr = Thermostat [ ctr_output ] . temp_target_level ;
2020-04-20 21:50:53 +01:00
// If time in ramp-up < max time
2020-05-05 19:40:09 +01:00
// AND temperature measured < target for heating or > for cooling
2020-05-02 21:33:29 +01:00
if ( ( time_in_rampup < = ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) )
2020-05-05 19:40:09 +01:00
& & ( ( ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_target_level )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_target_level )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) ) {
2020-04-20 21:50:53 +01:00
// DEADTIME point reached
// If temperature measured minus temperature at start of ramp-up >= threshold
// AND deadtime still 0
2020-10-10 14:19:11 +01:00
if ( ( abs ( temp_delta_rampup ) > = Thermostat [ ctr_output ] . temp_rampup_delta_out )
2020-05-02 21:33:29 +01:00
& & ( Thermostat [ ctr_output ] . time_rampup_deadtime = = 0 ) ) {
2020-04-20 21:50:53 +01:00
// Set deadtime, assuming it is half of the time until slope, since thermal inertia of the temp. fall needs to be considered
2020-10-10 14:19:11 +01:00
// minus open time of the valve (arround 3 minutes). If rise/sink very fast limit it to delay of output valve
2020-04-20 21:50:53 +01:00
int32_t time_aux ;
2020-05-02 21:33:29 +01:00
time_aux = ( ( time_in_rampup / 2 ) - Thermostat [ ctr_output ] . time_output_delay ) ;
if ( time_aux > = Thermostat [ ctr_output ] . time_output_delay ) {
Thermostat [ ctr_output ] . time_rampup_deadtime = ( uint32_t ) time_aux ;
2020-04-20 21:50:53 +01:00
}
else {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_rampup_deadtime = Thermostat [ ctr_output ] . time_output_delay ;
2020-04-20 21:50:53 +01:00
}
2020-05-05 19:40:09 +01:00
// Calculate absolute gradient since start of ramp-up (considering deadtime) in thousandths of º/hour
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_meas_gradient = ( int32_t ) ( ( 360000 * ( int32_t ) temp_delta_rampup ) / ( int32_t ) time_in_rampup ) ;
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_rampup_nextcycle = TasmotaGlobal . uptime + ( ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle * 60 ) ;
2020-04-20 21:50:53 +01:00
// Set auxiliary variables
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_cycle = Thermostat [ ctr_output ] . temp_measured ;
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_changepoint = TasmotaGlobal . uptime + ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_output_off = Thermostat [ ctr_output ] . temp_target_level_ctr ;
2020-04-20 21:50:53 +01:00
}
// Gradient calculation every time_rampup_cycle
2020-10-28 16:32:07 +00:00
else if ( ( Thermostat [ ctr_output ] . time_rampup_deadtime > 0 ) & & ( TasmotaGlobal . uptime > = Thermostat [ ctr_output ] . time_rampup_nextcycle ) ) {
2020-04-20 21:50:53 +01:00
// Calculate temp. gradient in º/hour and set again time_rampup_nextcycle and temp_rampup_cycle
// temp_rampup_meas_gradient = ((3600 * temp_delta_rampup) / (os.time() - time_rampup_nextcycle))
2020-05-02 21:33:29 +01:00
temp_delta_rampup = Thermostat [ ctr_output ] . temp_measured - Thermostat [ ctr_output ] . temp_rampup_cycle ;
2020-05-10 22:00:57 +01:00
uint32_t time_total_rampup = ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle * 60 * Thermostat [ ctr_output ] . counter_rampup_cycles ;
2020-04-20 21:50:53 +01:00
// Translate into gradient per hour (thousandths of ° per hour)
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_meas_gradient = int32_t ( ( 360000 * ( int32_t ) temp_delta_rampup ) / ( int32_t ) time_total_rampup ) ;
2020-05-05 19:40:09 +01:00
if ( ( ( Thermostat [ ctr_output ] . temp_rampup_meas_gradient > 0 )
2020-05-05 20:11:32 +01:00
& & ( ( flag_heating ) ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_rampup_meas_gradient < 0 )
2020-05-05 20:11:32 +01:00
& & ( ( ! flag_heating ) ) ) ) {
2020-04-20 21:50:53 +01:00
// Calculate time to switch Off and come out of ramp-up
// y-y1 = m(x-x1) -> x = ((y-y1) / m) + x1 -> y1 = temp_rampup_cycle, x1 = (time_rampup_nextcycle - time_rampup_cycle), m = gradient in º/sec
// Better Alternative -> (y-y1)/(x-x1) = ((y2-y1)/(x2-x1)) -> where y = temp (target) and x = time (to switch off, what its needed)
2020-10-10 14:19:11 +01:00
// x = ((y-y1)/(y2-y1))*(x2-x1) + x1 - deadtime
2020-05-05 19:40:09 +01:00
aux_temp_delta = Thermostat [ ctr_output ] . temp_target_level_ctr - Thermostat [ ctr_output ] . temp_rampup_cycle ;
Thermostat [ ctr_output ] . time_ctr_changepoint = ( uint32_t ) ( uint32_t ) ( ( ( uint32_t ) ( aux_temp_delta ) * ( uint32_t ) ( time_total_rampup ) ) / ( uint32_t ) temp_delta_rampup ) + ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_nextcycle - ( uint32_t ) time_total_rampup - ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_deadtime ;
2020-04-26 07:36:15 +01:00
2020-04-20 21:50:53 +01:00
// Calculate temperature for switching off the output
// y = (((y2-y1)/(x2-x1))*(x-x1)) + y1
2020-10-29 11:01:19 +00:00
Thermostat [ ctr_output ] . temp_rampup_output_off = ( int16_t ) ( ( ( int32_t ) temp_delta_rampup * ( int32_t ) ( Thermostat [ ctr_output ] . time_ctr_changepoint - ( TasmotaGlobal . uptime - ( time_total_rampup ) ) ) ) / ( int32_t ) ( time_total_rampup * Thermostat [ ctr_output ] . counter_rampup_cycles ) ) + Thermostat [ ctr_output ] . temp_rampup_cycle ;
2020-04-20 21:50:53 +01:00
// Set auxiliary variables
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_rampup_nextcycle = TasmotaGlobal . uptime + ( ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle * 60 ) ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_cycle = Thermostat [ ctr_output ] . temp_measured ;
2020-04-20 21:50:53 +01:00
// Reset period counter
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . counter_rampup_cycles = 1 ;
2020-04-20 21:50:53 +01:00
}
else {
// Increase the period counter
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . counter_rampup_cycles + + ;
2020-04-20 21:50:53 +01:00
// Set another period
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_rampup_nextcycle = TasmotaGlobal . uptime + ( ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle * 60 ) ;
2020-04-20 21:50:53 +01:00
// Reset time_ctr_changepoint and temp_rampup_output_off
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_changepoint = TasmotaGlobal . uptime + ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) - time_in_rampup ;
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_output_off = Thermostat [ ctr_output ] . temp_target_level_ctr ;
2020-04-20 21:50:53 +01:00
}
// Set time to get out of ramp-up
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = Thermostat [ ctr_output ] . time_ctr_changepoint + Thermostat [ ctr_output ] . time_rampup_deadtime ;
2020-04-20 21:50:53 +01:00
}
// Set output switch ON or OFF
// If deadtime has not been calculated
// or checkpoint has not been calculated
// or it is not yet time and temperature to switch it off acc. to calculations
2020-05-05 19:40:09 +01:00
// or gradient is <= 0 for heating of >= 0 for cooling
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . time_rampup_deadtime = = 0 )
| | ( Thermostat [ ctr_output ] . time_ctr_checkpoint = = 0 )
2020-10-28 16:32:07 +00:00
| | ( TasmotaGlobal . uptime < Thermostat [ ctr_output ] . time_ctr_changepoint )
2020-05-05 19:40:09 +01:00
| | ( ( ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_rampup_output_off )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_rampup_output_off )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) )
2020-05-05 19:40:09 +01:00
| | ( ( ( Thermostat [ ctr_output ] . temp_rampup_meas_gradient < = 0 )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_rampup_meas_gradient > = 0 )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . command_output = IFACE_ON ;
2020-04-20 21:50:53 +01:00
}
else {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
2020-04-20 21:50:53 +01:00
}
}
else {
// If we have not reached the temperature, start with an initial value for accumulated error for the PI controller
2020-05-05 19:40:09 +01:00
if ( ( ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_target_level_ctr )
2020-05-05 20:11:32 +01:00
& & ( flag_heating ) )
2020-05-05 19:40:09 +01:00
| | ( ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_target_level_ctr )
2020-05-05 20:11:32 +01:00
& & ( ! flag_heating ) ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_accum_error = Thermostat [ ctr_output ] . temp_rampup_pi_acc_error ;
2020-04-20 21:50:53 +01:00
}
// Set to now time to get out of ramp-up
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = TasmotaGlobal . uptime ;
2020-04-20 21:50:53 +01:00
// Switch Off output
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
void ThermostatPeakDetectorInit ( uint8_t ctr_output )
{
for ( uint8_t i = 0 ; i < THERMOSTAT_PEAKNUMBER_AUTOTUNE ; i + + ) {
Thermostat [ ctr_output ] . temp_peaks_atune [ i ] = 0 ;
}
Thermostat [ ctr_output ] . pU_pi_atune = 0 ;
Thermostat [ ctr_output ] . kP_pi_atune = 0 ;
Thermostat [ ctr_output ] . kI_pi_atune = 0 ;
2020-10-10 14:19:11 +01:00
Thermostat [ ctr_output ] . kU_pi_atune = 0 ;
Thermostat [ ctr_output ] . peak_ctr = 0 ;
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . temp_abs_max_atune = 0 ;
Thermostat [ ctr_output ] . temp_abs_min_atune = 100 ;
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = TasmotaGlobal . uptime + THERMOSTAT_TIME_MAX_AUTOTUNE ;
2020-05-14 21:44:32 +01:00
}
void ThermostatPeakDetector ( uint8_t ctr_output )
{
uint8_t peak_num = Thermostat [ ctr_output ] . peak_ctr ;
int16_t peak_avg = 0 ;
bool peak_transition = false ;
// Update Max/Min Thermostat[ctr_output].temp_abs_max_atune
if ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_abs_max_atune ) {
Thermostat [ ctr_output ] . temp_abs_max_atune = Thermostat [ ctr_output ] . temp_measured ;
}
if ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_abs_min_atune ) {
Thermostat [ ctr_output ] . temp_abs_min_atune = Thermostat [ ctr_output ] . temp_measured ;
2020-10-10 14:19:11 +01:00
}
2020-05-14 21:44:32 +01:00
// For heating, even peak numbers look for maxes, odd for minds, the contrary for cooling
// If we did not found all peaks yet
if ( peak_num < THERMOSTAT_PEAKNUMBER_AUTOTUNE ) {
bool flag_heating = ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_HEATING ) ;
bool cond_peak_1 = ( ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] )
& & ( flag_heating )
| | ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] )
& & ( ! flag_heating ) ) ;
bool cond_peak_2 = ( ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] )
& & ( flag_heating )
| | ( Thermostat [ ctr_output ] . temp_measured > Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] )
& & ( ! flag_heating ) ) ;
bool cond_gradient_1 = ( ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 )
& & ( flag_heating )
| | ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 )
& & ( ! flag_heating ) ) ;
bool cond_gradient_2 = ( ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 )
& & ( flag_heating )
| | ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 )
& & ( ! flag_heating ) ) ;
// If peak number is even (look for max if heating and min if cooling)
if ( ( peak_num % 2 ) = = 0 ) {
// If current temperature higher (heating) or lower (cooling) than registered value for peak
// AND temperature gradient > 0 for heating or < 0 for cooling
// then, update value
if ( cond_peak_1 & & cond_gradient_1 ) {
Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] = Thermostat [ ctr_output ] . temp_measured ;
}
// Else if current temperature lower (heating) or higher (cooling) then registered value for peak
// AND difference to peak is outside of the peak no detection band
// then the current peak value is the peak (max for heating, min for cooling), switch detection
if ( ( cond_peak_2 )
& & ( abs ( Thermostat [ ctr_output ] . temp_measured - Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] ) > Thermostat [ ctr_output ] . temp_band_no_peak_det ) ) {
// Register peak timestamp;
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_peak_timestamps_atune [ peak_num ] = ( TasmotaGlobal . uptime / 60 ) ;
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . peak_ctr + + ;
peak_transition = true ;
}
}
// Peak number is odd (look for min if heating and max if cooling)
else {
// If current temperature lower (heating) or higher (cooling) than registered value for peak
// AND temperature gradient < 0 for heating or > 0 for cooling
// then, update value
if ( cond_peak_2 & & cond_gradient_2 ) {
Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] = Thermostat [ ctr_output ] . temp_measured ;
}
// Else if current temperature higher (heating) or lower (cooling) then registered value for peak
// AND difference to peak is outside of the peak no detection band
// then the current peak value is the peak (min for heating, max for cooling), switch detection
if ( ( cond_peak_1 )
& & ( abs ( Thermostat [ ctr_output ] . temp_measured - Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num ] ) > Thermostat [ ctr_output ] . temp_band_no_peak_det ) ) {
2020-10-10 14:19:11 +01:00
// Calculate period
2020-05-14 21:44:32 +01:00
// Register peak timestamp;
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_peak_timestamps_atune [ peak_num ] = ( TasmotaGlobal . uptime / 60 ) ;
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . peak_ctr + + ;
peak_transition = true ;
}
}
}
else {
// Peak detection done, proceed to evaluate results
ThermostatAutotuneParamCalc ( ctr_output ) ;
2020-05-14 22:35:45 +01:00
Thermostat [ ctr_output ] . status . autotune_flag = AUTOTUNE_OFF ;
2020-05-14 21:44:32 +01:00
}
2020-10-10 14:19:11 +01:00
2020-05-14 21:44:32 +01:00
// If peak detection not finalized but bigger than 3 and we have just found a peak, check if results can be extracted
if ( ( Thermostat [ ctr_output ] . peak_ctr > 2 ) & & ( peak_transition ) ) {
//Update peak_num
peak_num = Thermostat [ ctr_output ] . peak_ctr ;
// Calculate average value among the last 3 peaks
2020-10-10 14:19:11 +01:00
peak_avg = ( abs ( Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num - 1 ]
2020-05-14 21:44:32 +01:00
- Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num - 2 ] )
2020-10-10 14:19:11 +01:00
+ abs ( Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num - 2 ]
2020-05-14 21:44:32 +01:00
- Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num - 3 ] ) ) / 2 ;
2020-10-10 14:19:11 +01:00
2020-05-14 21:44:32 +01:00
if ( ( 20 * ( int32_t ) peak_avg ) < ( int32_t ) ( Thermostat [ ctr_output ] . temp_abs_max_atune - Thermostat [ ctr_output ] . temp_abs_min_atune ) ) {
// Calculate average temperature among all peaks
for ( uint8_t i = 0 ; i < peak_num ; i + + ) {
peak_avg + = Thermostat [ ctr_output ] . temp_peaks_atune [ i ] ;
}
peak_avg / = peak_num ;
// If last period crosses the average value, result valid
if ( 10 * abs ( Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num - 1 ] - Thermostat [ ctr_output ] . temp_peaks_atune [ peak_num - 2 ] ) < ( Thermostat [ ctr_output ] . temp_abs_max_atune - peak_avg ) ) {
// Peak detection done, proceed to evaluate results
ThermostatAutotuneParamCalc ( ctr_output ) ;
2020-05-14 22:35:45 +01:00
Thermostat [ ctr_output ] . status . autotune_flag = AUTOTUNE_OFF ;
2020-05-14 21:44:32 +01:00
}
}
}
peak_transition = false ;
}
void ThermostatAutotuneParamCalc ( uint8_t ctr_output )
{
uint8_t peak_num = Thermostat [ ctr_output ] . peak_ctr ;
// Calculate the tunning parameters
// Resolution increased to avoid float operations
Thermostat [ ctr_output ] . kU_pi_atune = ( uint16_t ) ( 100 * ( ( uint32_t ) 400000 * ( uint32_t ) ( Thermostat [ ctr_output ] . dutycycle_step_autotune ) ) / ( ( uint32_t ) ( Thermostat [ ctr_output ] . temp_abs_max_atune - Thermostat [ ctr_output ] . temp_abs_min_atune ) * ( uint32_t ) 314159 ) ) ;
Thermostat [ ctr_output ] . pU_pi_atune = ( Thermostat [ ctr_output ] . time_peak_timestamps_atune [ peak_num - 1 ] - Thermostat [ ctr_output ] . time_peak_timestamps_atune [ peak_num - 2 ] ) ;
2020-10-10 14:19:11 +01:00
2020-05-14 21:44:32 +01:00
switch ( Thermostat [ ctr_output ] . status . autotune_perf_mode ) {
case AUTOTUNE_PERF_FAST :
// Calculate kP/Ki autotune
Thermostat [ ctr_output ] . kP_pi_atune = ( 4 * Thermostat [ ctr_output ] . kU_pi_atune ) / 10 ;
break ;
case AUTOTUNE_PERF_NORMAL :
// Calculate kP/Ki autotune
Thermostat [ ctr_output ] . kP_pi_atune = ( 18 * Thermostat [ ctr_output ] . kU_pi_atune ) / 100 ;
break ;
case AUTOTUNE_PERF_SLOW :
// Calculate kP/Ki autotune
Thermostat [ ctr_output ] . kP_pi_atune = ( 13 * Thermostat [ ctr_output ] . kU_pi_atune ) / 100 ;
break ;
}
// Resolution increased to avoid float operations
Thermostat [ ctr_output ] . kI_pi_atune = ( 12 * ( 6000 * Thermostat [ ctr_output ] . kU_pi_atune / Thermostat [ ctr_output ] . pU_pi_atune ) ) / 10 ;
// Calculate PropBand Autotune
Thermostat [ ctr_output ] . val_prop_band_atune = 100 / Thermostat [ ctr_output ] . kP_pi_atune ;
// Calculate Reset Time Autotune
Thermostat [ ctr_output ] . time_reset_atune = ( uint32_t ) ( ( ( ( uint32_t ) Thermostat [ ctr_output ] . kP_pi_atune * ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 6000 ) ) / ( uint32_t ) Thermostat [ ctr_output ] . kI_pi_atune ) ;
}
void ThermostatWorkAutomaticPIAutotune ( uint8_t ctr_output )
{
bool flag_heating = ( Thermostat [ ctr_output ] . status . climate_mode = = CLIMATE_HEATING ) ;
// If no timeout of the PI Autotune function
2020-05-14 22:35:45 +01:00
// AND no change in setpoint
2020-10-28 16:32:07 +00:00
if ( ( TasmotaGlobal . uptime < Thermostat [ ctr_output ] . time_ctr_checkpoint )
2020-05-14 22:35:45 +01:00
& & ( Thermostat [ ctr_output ] . temp_target_level_ctr = = Thermostat [ ctr_output ] . temp_target_level ) ) {
2020-10-28 16:32:07 +00:00
if ( TasmotaGlobal . uptime > = Thermostat [ ctr_output ] . time_ctr_checkpoint ) {
2020-10-10 14:19:11 +01:00
Thermostat [ ctr_output ] . temp_target_level_ctr = Thermostat [ ctr_output ] . temp_target_level ;
2020-05-14 21:44:32 +01:00
// Calculate time_ctr_changepoint
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_changepoint = TasmotaGlobal . uptime + ( ( ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * ( uint32_t ) Thermostat [ ctr_output ] . dutycycle_step_autotune ) / ( uint32_t ) 100 ) ;
2020-05-14 21:44:32 +01:00
// Reset cycle active
Thermostat [ ctr_output ] . status . status_cycle_active = CYCLE_OFF ;
}
// Set Output On/Off depending on the changepoint
2020-10-28 16:32:07 +00:00
if ( TasmotaGlobal . uptime < Thermostat [ ctr_output ] . time_ctr_changepoint ) {
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . status . status_cycle_active = CYCLE_ON ;
Thermostat [ ctr_output ] . status . command_output = IFACE_ON ;
}
else {
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
}
// Update peak values
ThermostatPeakDetector ( ctr_output ) ;
}
else {
// Disable Autotune flag
Thermostat [ ctr_output ] . status . autotune_flag = AUTOTUNE_OFF ;
}
if ( Thermostat [ ctr_output ] . status . autotune_flag = = AUTOTUNE_OFF ) {
// Set output Off
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
}
}
# endif //USE_PI_AUTOTUNING
2020-05-02 21:33:29 +01:00
void ThermostatCtrWork ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 21:33:29 +01:00
switch ( Thermostat [ ctr_output ] . status . controller_mode ) {
2020-05-02 19:31:20 +01:00
// Hybrid controller (Ramp-up + PI)
case CTR_HYBRID :
2020-05-02 21:33:29 +01:00
switch ( Thermostat [ ctr_output ] . status . phase_hybrid_ctr ) {
2020-04-20 21:50:53 +01:00
case CTR_HYBRID_RAMP_UP :
2020-05-02 21:33:29 +01:00
ThermostatWorkAutomaticRampUp ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
break ;
case CTR_HYBRID_PI :
2020-05-02 21:33:29 +01:00
ThermostatWorkAutomaticPI ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
break ;
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_HYBRID_PI_AUTOTUNE :
ThermostatWorkAutomaticPIAutotune ( ctr_output ) ;
break ;
# endif //USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
}
break ;
2020-05-02 19:31:20 +01:00
// PI controller
case CTR_PI :
2020-05-02 21:33:29 +01:00
ThermostatWorkAutomaticPI ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
break ;
2020-05-02 19:31:20 +01:00
// Ramp-up controller (predictive)
case CTR_RAMP_UP :
2020-05-02 21:33:29 +01:00
ThermostatWorkAutomaticRampUp ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
break ;
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_PI_AUTOTUNE :
ThermostatWorkAutomaticPIAutotune ( ctr_output ) ;
break ;
# endif //USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
}
}
2020-05-02 21:33:29 +01:00
void ThermostatWork ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 21:33:29 +01:00
switch ( Thermostat [ ctr_output ] . status . thermostat_mode ) {
2020-05-02 19:31:20 +01:00
// State if thermostat Off or Emergency
case THERMOSTAT_OFF :
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . command_output = IFACE_OFF ;
2020-04-20 21:50:53 +01:00
break ;
2020-05-02 19:31:20 +01:00
// State automatic thermostat active following to command target temp.
case THERMOSTAT_AUTOMATIC_OP :
2020-05-02 21:33:29 +01:00
ThermostatCtrWork ( ctr_output ) ;
2020-10-10 14:19:11 +01:00
2020-04-20 21:50:53 +01:00
break ;
2020-05-02 19:31:20 +01:00
// State manual operation following input switch
case THERMOSTAT_MANUAL_OP :
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = 0 ;
2020-10-10 14:19:11 +01:00
Thermostat [ ctr_output ] . status . command_output = Thermostat [ ctr_output ] . status . status_input ;
2020-04-20 21:50:53 +01:00
break ;
}
2020-05-02 21:33:29 +01:00
ThermostatOutputRelay ( ctr_output , Thermostat [ ctr_output ] . status . command_output ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
void ThermostatDiagnostics ( uint8_t ctr_output )
2020-10-10 14:19:11 +01:00
{
2020-05-02 19:31:20 +01:00
// Diagnostic related to the plausibility of the output state
2020-05-14 21:44:32 +01:00
if ( ( Thermostat [ ctr_output ] . diag . diagnostic_mode = = DIAGNOSTIC_ON )
& & ( Thermostat [ ctr_output ] . diag . output_inconsist_ctr > = THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_OFF ;
2020-10-10 14:19:11 +01:00
Thermostat [ ctr_output ] . diag . state_emergency = EMERGENCY_ON ;
2020-05-02 19:31:20 +01:00
}
2020-10-10 14:19:11 +01:00
// Diagnostic related to the plausibility of the output power implemented
2020-05-02 19:31:20 +01:00
// already into the energy driver
2020-05-02 21:33:29 +01:00
// If diagnostics fail, emergency enabled and thermostat shutdown triggered
2020-05-14 21:44:32 +01:00
if ( Thermostat [ ctr_output ] . diag . state_emergency = = EMERGENCY_ON ) {
2020-05-02 21:33:29 +01:00
ThermostatEmergencyShutdown ( ctr_output ) ;
2020-05-02 19:31:20 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
void ThermostatController ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
2020-05-02 21:33:29 +01:00
ThermostatState ( ctr_output ) ;
ThermostatWork ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
bool ThermostatTimerArm ( uint8_t ctr_output , int16_t tempVal )
2020-04-24 22:18:17 +01:00
{
bool result = false ;
// TempVal unit is tenths of degrees celsius
2020-10-10 14:19:11 +01:00
if ( ( tempVal > = - 1000 )
2020-04-24 22:18:17 +01:00
& & ( tempVal < = 1000 )
2020-05-02 21:33:29 +01:00
& & ( tempVal > = ( int16_t ) Thermostat [ ctr_output ] . temp_frost_protect ) ) {
Thermostat [ ctr_output ] . temp_target_level = tempVal ;
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_AUTOMATIC_OP ;
2020-04-24 22:18:17 +01:00
result = true ;
}
// Returns true if setpoint plausible and thermostat armed, false on the contrary
return result ;
}
2020-05-02 21:33:29 +01:00
void ThermostatTimerDisarm ( uint8_t ctr_output )
2020-04-24 22:18:17 +01:00
{
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_target_level = THERMOSTAT_TEMP_INIT ;
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_OFF ;
2020-04-24 22:18:17 +01:00
}
2020-04-20 21:50:53 +01:00
# ifdef DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
void ThermostatVirtualSwitch ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
char domoticz_in_topic [ ] = DOMOTICZ_IN_TOPIC ;
2020-05-03 14:11:19 +01:00
if ( ctr_output < DOMOTICZ_MAX_IDX ) {
Response_P ( DOMOTICZ_MES , Domoticz_Virtual_Switches [ ctr_output ] , ( 0 = = Thermostat [ ctr_output ] . status . command_output ) ? 0 : 1 , " " ) ;
MqttPublish ( domoticz_in_topic ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
void ThermostatVirtualSwitchCtrState ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
char domoticz_in_topic [ ] = DOMOTICZ_IN_TOPIC ;
2020-05-03 14:11:19 +01:00
Response_P ( DOMOTICZ_MES , DOMOTICZ_IDX2 , ( 0 = = Thermostat [ 0 ] . status . phase_hybrid_ctr ) ? 0 : 1 , " " ) ;
2020-04-20 21:50:53 +01:00
MqttPublish ( domoticz_in_topic ) ;
}
2020-05-02 19:31:20 +01:00
2020-05-02 21:33:29 +01:00
void ThermostatDebug ( uint8_t ctr_output )
2020-05-02 19:31:20 +01:00
{
char result_chr [ FLOATSZ ] ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " " ) ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " ------ Thermostat Start ------ " ) ) ;
2020-05-02 21:33:29 +01:00
dtostrfd ( Thermostat [ ctr_output ] . status . counter_seconds , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.counter_seconds: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . thermostat_mode , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.thermostat_mode: %s " ) , result_chr ) ;
2020-05-14 21:44:32 +01:00
dtostrfd ( Thermostat [ ctr_output ] . diag . state_emergency , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].diag.state_emergency: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . diag . output_inconsist_ctr , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].diag.output_inconsist_ctr: %s " ) , result_chr ) ;
2020-05-02 21:33:29 +01:00
dtostrfd ( Thermostat [ ctr_output ] . status . controller_mode , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.controller_mode: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . command_output , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.command_output: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . status_output , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.status_output: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . status_input , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.status_input: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . phase_hybrid_ctr , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.phase_hybrid_ctr: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . sensor_alive , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.sensor_alive: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . status_cycle_active , 0 , result_chr ) ;
2020-10-10 14:19:11 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.status_cycle_active: %s " ) , result_chr ) ;
2020-05-02 21:33:29 +01:00
dtostrfd ( Thermostat [ ctr_output ] . temp_pi_error , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].temp_pi_error: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . temp_pi_accum_error , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].temp_pi_accum_error: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . time_proportional_pi , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].time_proportional_pi: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . time_integral_pi , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].time_integral_pi: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . time_total_pi , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].time_total_pi: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . temp_measured_gradient , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].temp_measured_gradient: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . time_rampup_deadtime , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].time_rampup_deadtime: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . temp_rampup_meas_gradient , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].temp_rampup_meas_gradient: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . time_ctr_changepoint , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].time_ctr_changepoint: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . temp_rampup_output_off , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].temp_rampup_output_off: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . time_ctr_checkpoint , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].time_ctr_checkpoint: %s " ) , result_chr ) ;
2020-10-28 16:32:07 +00:00
dtostrfd ( TasmotaGlobal . uptime , 0 , result_chr ) ;
2020-05-02 19:31:20 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " uptime: %s " ) , result_chr ) ;
2020-10-28 18:03:39 +00:00
dtostrfd ( TasmotaGlobal . power , 0 , result_chr ) ;
2020-05-02 19:31:20 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " power: %s " ) , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " ------ Thermostat End ------ " ) ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " " ) ) ;
}
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
void ThermostatGetLocalSensor ( uint8_t ctr_output ) {
2020-10-30 11:29:48 +00:00
String buf = TasmotaGlobal . mqtt_data ; // copy the string into a new buffer that will be modified
2020-10-10 14:19:11 +01:00
JsonParser parser ( ( char * ) buf . c_str ( ) ) ;
2020-09-23 18:38:24 +01:00
JsonParserObject root = parser . getRootObject ( ) ;
2020-09-23 07:45:14 +01:00
if ( root ) {
2020-10-10 14:19:11 +01:00
JsonParserToken value_token = root [ PSTR ( THERMOSTAT_SENSOR_NAME ) ] . getObject ( ) [ PSTR ( " Temperature " ) ] ;
2020-09-23 07:45:14 +01:00
if ( value_token . isNum ( ) ) {
int16_t value = value_token . getFloat ( ) * 10 ;
2020-07-13 19:22:37 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
2020-09-23 07:45:14 +01:00
value = ThermostatFahrenheitToCelsius ( value , TEMP_CONV_ABSOLUTE ) ;
2020-07-13 19:22:37 +01:00
}
2020-10-10 14:19:11 +01:00
if ( ( value > = - 1000 )
2020-04-29 20:42:20 +01:00
& & ( value < = 1000 )
2020-05-02 21:33:29 +01:00
& & ( Thermostat [ ctr_output ] . status . sensor_type = = SENSOR_LOCAL ) ) {
2020-10-28 16:32:07 +00:00
uint32_t timestamp = TasmotaGlobal . uptime ;
2020-04-29 20:42:20 +01:00
// Calculate temperature gradient if temperature value has changed
2020-05-02 21:33:29 +01:00
if ( value ! = Thermostat [ ctr_output ] . temp_measured ) {
int32_t temp_delta = ( value - Thermostat [ ctr_output ] . temp_measured ) ; // in tenths of degrees
uint32_t time_delta = ( timestamp - Thermostat [ ctr_output ] . timestamp_temp_meas_change_update ) ; // in seconds
2020-05-05 19:40:09 +01:00
Thermostat [ ctr_output ] . temp_measured_gradient = ( int32_t ) ( ( 360000 * temp_delta ) / ( ( int32_t ) time_delta ) ) ; // thousandths of degrees per hour
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_measured = value ;
Thermostat [ ctr_output ] . timestamp_temp_meas_change_update = timestamp ;
2020-04-29 20:42:20 +01:00
}
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . timestamp_temp_measured_update = timestamp ;
Thermostat [ ctr_output ] . status . sensor_alive = IFACE_ON ;
2020-04-29 20:42:20 +01:00
}
}
}
}
2020-04-20 21:50:53 +01:00
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CmndThermostatModeSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( CharToFloat ( XdrvMailbox . data ) ) ;
if ( ( value > = THERMOSTAT_OFF ) & & ( value < THERMOSTAT_MODES_MAX ) ) {
Thermostat [ ctr_output ] . status . thermostat_mode = value ;
Thermostat [ ctr_output ] . timestamp_input_on = 0 ; // Reset last manual switch timer if command set externally
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . thermostat_mode ) ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-05 19:40:09 +01:00
void CmndClimateModeSet ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( CharToFloat ( XdrvMailbox . data ) ) ;
if ( ( value > = CLIMATE_HEATING ) & & ( value < CLIMATE_MODES_MAX ) ) {
Thermostat [ ctr_output ] . status . climate_mode = value ;
2020-05-14 21:44:32 +01:00
// Trigger a restart of the controller
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = TasmotaGlobal . uptime ;
2020-05-05 19:40:09 +01:00
}
}
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . climate_mode ) ;
}
}
2020-04-20 21:50:53 +01:00
void CmndTempFrostProtectSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
int16_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ( int16_t ) ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) , TEMP_CONV_ABSOLUTE ) ;
}
else {
value = ( int16_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = 0 )
2020-05-02 21:33:29 +01:00
& & ( value < = 127 ) ) {
Thermostat [ ctr_output ] . temp_frost_protect = ( uint8_t ) value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_frost_protect , TEMP_CONV_ABSOLUTE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = ( int16_t ) Thermostat [ ctr_output ] . temp_frost_protect ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 10 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndControllerModeSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ( value > = CTR_HYBRID ) & & ( value < CTR_MODES_MAX ) ) {
Thermostat [ ctr_output ] . status . controller_mode = value ;
2020-05-14 21:44:32 +01:00
// Reset controller variables
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . timestamp_rampup_start = TasmotaGlobal . uptime ;
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . temp_rampup_start = Thermostat [ ctr_output ] . temp_measured ;
Thermostat [ ctr_output ] . temp_rampup_meas_gradient = 0 ;
Thermostat [ ctr_output ] . time_rampup_deadtime = 0 ;
Thermostat [ ctr_output ] . counter_rampup_cycles = 1 ;
Thermostat [ ctr_output ] . time_ctr_changepoint = 0 ;
Thermostat [ ctr_output ] . time_ctr_checkpoint = 0 ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . controller_mode ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndInputSwitchSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ThermostatSwitchIdValid ( value ) ) {
Thermostat [ ctr_output ] . status . input_switch_number = value ;
2020-10-28 16:32:07 +00:00
Thermostat [ ctr_output ] . timestamp_input_on = TasmotaGlobal . uptime ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . input_switch_number ) ;
2020-04-20 21:50:53 +01:00
}
2020-04-29 20:42:20 +01:00
}
2020-05-03 14:11:19 +01:00
void CmndInputSwitchUse ( void )
{
if ( ( XdrvMailbox . index > = INPUT_NOT_USED ) & & ( XdrvMailbox . index < = INPUT_USED ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
Thermostat [ ctr_output ] . status . use_input = ( uint32_t ) ( XdrvMailbox . payload ) ;
}
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . use_input ) ;
}
}
2020-04-29 20:42:20 +01:00
void CmndSensorInputSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ( value > = SENSOR_MQTT ) & & ( value < SENSOR_MAX ) ) {
Thermostat [ ctr_output ] . status . sensor_type = value ;
}
2020-04-29 20:42:20 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . sensor_type ) ;
2020-04-29 20:42:20 +01:00
}
2020-04-20 21:50:53 +01:00
}
void CmndOutputRelaySet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ThermostatRelayIdValid ( value ) ) {
Thermostat [ ctr_output ] . status . output_relay_number = value ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . output_relay_number ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeAllowRampupSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-14 21:44:32 +01:00
if ( ( value > = 0 ) & & ( value < 1440 ) ) {
Thermostat [ ctr_output ] . time_allow_rampup = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-14 21:44:32 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_allow_rampup ) ) ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-01 20:28:58 +01:00
void CmndTempFormatSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ( value > = 0 ) & & ( value < = TEMP_FAHRENHEIT ) ) {
Thermostat [ ctr_output ] . status . temp_format = value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . temp_format ) ;
2020-05-01 20:28:58 +01:00
}
}
2020-04-20 21:50:53 +01:00
void CmndTempMeasuredSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
int16_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) , TEMP_CONV_ABSOLUTE ) ;
}
else {
value = ( int16_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = - 1000 )
2020-05-02 21:33:29 +01:00
& & ( value < = 1000 )
& & ( Thermostat [ ctr_output ] . status . sensor_type = = SENSOR_MQTT ) ) {
2020-10-28 16:32:07 +00:00
uint32_t timestamp = TasmotaGlobal . uptime ;
2020-05-02 21:33:29 +01:00
// Calculate temperature gradient if temperature value has changed
if ( value ! = Thermostat [ ctr_output ] . temp_measured ) {
int32_t temp_delta = ( value - Thermostat [ ctr_output ] . temp_measured ) ; // in tenths of degrees
uint32_t time_delta = ( timestamp - Thermostat [ ctr_output ] . timestamp_temp_meas_change_update ) ; // in seconds
2020-05-05 19:40:09 +01:00
Thermostat [ ctr_output ] . temp_measured_gradient = ( int32_t ) ( ( 360000 * temp_delta ) / ( ( int32_t ) time_delta ) ) ; // thousandths of degrees per hour
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_measured = value ;
Thermostat [ ctr_output ] . timestamp_temp_meas_change_update = timestamp ;
}
Thermostat [ ctr_output ] . timestamp_temp_measured_update = timestamp ;
Thermostat [ ctr_output ] . status . sensor_alive = IFACE_ON ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_measured , TEMP_CONV_ABSOLUTE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = Thermostat [ ctr_output ] . temp_measured ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 10 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTempTargetSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
int16_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) , TEMP_CONV_ABSOLUTE ) ;
}
else {
value = ( int16_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = - 1000 )
2020-05-02 21:33:29 +01:00
& & ( value < = 1000 )
& & ( value > = ( int16_t ) Thermostat [ ctr_output ] . temp_frost_protect ) ) {
Thermostat [ ctr_output ] . temp_target_level = value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_target_level , TEMP_CONV_ABSOLUTE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = Thermostat [ ctr_output ] . temp_target_level ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 10 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTempMeasuredGrdRead ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
int16_t value ;
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_measured_gradient , TEMP_CONV_RELATIVE ) ;
}
else {
value = Thermostat [ ctr_output ] . temp_measured_gradient ;
}
2020-05-10 22:00:57 +01:00
ResponseCmndFloat ( ( ( float ) value ) / 1000 , 1 ) ;
2020-05-01 20:28:58 +01:00
}
2020-04-20 21:50:53 +01:00
}
void CmndStateEmergencySet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ( value > = 0 ) & & ( value < = 1 ) ) {
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . diag . state_emergency = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-14 21:44:32 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . diag . state_emergency ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeManualToAutoSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
Thermostat [ ctr_output ] . time_manual_to_auto = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-10 22:00:57 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_manual_to_auto ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndPropBandSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ( value > = 0 ) & & ( value < = 20 ) ) {
Thermostat [ ctr_output ] . val_prop_band = value ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . val_prop_band ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeResetSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_reset = value ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . time_reset ) ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-10 22:00:57 +01:00
void CmndTimePiProportRead ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . time_proportional_pi ) ;
}
}
void CmndTimePiIntegrRead ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . time_integral_pi ) ;
}
}
2020-04-20 21:50:53 +01:00
void CmndTimePiCycleSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
2020-05-10 22:34:53 +01:00
Thermostat [ ctr_output ] . time_pi_cycle = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-10 22:00:57 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTempAntiWindupResetSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
uint8_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ( uint8_t ) ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) , TEMP_CONV_RELATIVE ) ;
}
else {
value = ( uint8_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = 0 )
2020-05-02 21:33:29 +01:00
& & ( value < = 100 ) ) {
Thermostat [ ctr_output ] . temp_reset_anti_windup = value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_reset_anti_windup , TEMP_CONV_RELATIVE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = Thermostat [ ctr_output ] . temp_reset_anti_windup ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 10 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTempHystSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
int8_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ( int8_t ) ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) , TEMP_CONV_RELATIVE ) ;
}
else {
value = ( int8_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = - 100 )
2020-05-02 21:33:29 +01:00
& & ( value < = 100 ) ) {
Thermostat [ ctr_output ] . temp_hysteresis = value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_hysteresis , TEMP_CONV_RELATIVE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = Thermostat [ ctr_output ] . temp_hysteresis ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 10 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-14 21:44:32 +01:00
# ifdef USE_PI_AUTOTUNING
void CmndPerfLevelAutotune ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( XdrvMailbox . payload ) ;
if ( ( value > = 0 ) & & ( value < = AUTOTUNE_PERF_MAX ) ) {
Thermostat [ ctr_output ] . status . autotune_perf_mode = value ;
}
}
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . autotune_perf_mode ) ;
}
}
# endif // USE_PI_AUTOTUNING
2020-04-20 21:50:53 +01:00
void CmndTimeMaxActionSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
Thermostat [ ctr_output ] . time_max_action = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-10 22:00:57 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_max_action ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeMinActionSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
Thermostat [ ctr_output ] . time_min_action = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-10 22:00:57 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_min_action ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeSensLostSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
Thermostat [ ctr_output ] . time_sens_lost = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-10 22:00:57 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_sens_lost ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeMinTurnoffActionSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
Thermostat [ ctr_output ] . time_min_turnoff_action = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-10 22:00:57 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_min_turnoff_action ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTempRupDeltInSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
uint8_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ( uint8_t ) ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) , TEMP_CONV_RELATIVE ) ;
}
else {
value = ( uint8_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = 0 )
2020-05-02 21:33:29 +01:00
& & ( value < = 100 ) ) {
Thermostat [ ctr_output ] . temp_rampup_delta_in = value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_rampup_delta_in , TEMP_CONV_RELATIVE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = Thermostat [ ctr_output ] . temp_rampup_delta_in ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 10 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTempRupDeltOutSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
uint8_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ( uint8_t ) ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) , TEMP_CONV_RELATIVE ) ;
}
else {
value = ( uint8_t ) ( CharToFloat ( XdrvMailbox . data ) * 10 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = 0 )
2020-05-02 21:33:29 +01:00
& & ( value < = 100 ) ) {
Thermostat [ ctr_output ] . temp_rampup_delta_out = value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_rampup_delta_out , TEMP_CONV_RELATIVE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = Thermostat [ ctr_output ] . temp_rampup_delta_out ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 10 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeRampupMaxSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
Thermostat [ ctr_output ] . time_rampup_max = ( uint16_t ) value ;
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
2020-05-10 22:00:57 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeRampupCycleSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint32_t value = ( uint32_t ) ( XdrvMailbox . payload ) ;
2020-05-10 22:00:57 +01:00
if ( ( value > = 0 ) & & ( value < = 1440 ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_rampup_cycle = ( uint16_t ) value ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . time_rampup_cycle ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTempRampupPiAccErrSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
uint16_t value ;
if ( XdrvMailbox . data_len > 0 ) {
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ( uint16_t ) ThermostatFahrenheitToCelsius ( ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 100 ) , TEMP_CONV_RELATIVE ) ;
}
else {
value = ( uint16_t ) ( CharToFloat ( XdrvMailbox . data ) * 100 ) ;
}
2020-10-10 14:19:11 +01:00
if ( ( value > = 0 )
2020-05-02 21:33:29 +01:00
& & ( value < = 2500 ) ) {
Thermostat [ ctr_output ] . temp_rampup_pi_acc_error = value ;
}
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . temp_format = = TEMP_FAHRENHEIT ) {
value = ThermostatCelsiusToFahrenheit ( ( int32_t ) Thermostat [ ctr_output ] . temp_rampup_pi_acc_error , TEMP_CONV_RELATIVE ) ;
2020-05-01 20:28:58 +01:00
}
2020-05-02 21:33:29 +01:00
else {
value = Thermostat [ ctr_output ] . temp_rampup_pi_acc_error ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndFloat ( ( float ) value / 100 , 1 ) ;
2020-04-20 21:50:53 +01:00
}
}
2020-05-02 19:31:20 +01:00
void CmndDiagnosticModeSet ( void )
{
2020-05-02 21:33:29 +01:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( CharToFloat ( XdrvMailbox . data ) ) ;
if ( ( value > = DIAGNOSTIC_OFF ) & & ( value < = DIAGNOSTIC_ON ) ) {
2020-05-14 21:44:32 +01:00
Thermostat [ ctr_output ] . diag . diagnostic_mode = value ;
2020-05-02 21:33:29 +01:00
}
2020-05-02 19:31:20 +01:00
}
2020-05-14 21:44:32 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . diag . diagnostic_mode ) ;
2020-05-02 19:31:20 +01:00
}
}
2020-05-20 21:10:49 +01:00
void CmndCtrDutyCycleRead ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
uint8_t value = 0 ;
if ( ( Thermostat [ ctr_output ] . status . controller_mode = = CTR_PI )
| | ( ( Thermostat [ ctr_output ] . status . controller_mode = = CTR_HYBRID )
& & ( Thermostat [ ctr_output ] . status . phase_hybrid_ctr = = CTR_HYBRID_PI ) ) ) {
value = Thermostat [ ctr_output ] . time_total_pi / Thermostat [ ctr_output ] . time_pi_cycle ;
}
else if ( ( Thermostat [ ctr_output ] . status . controller_mode = = CTR_RAMP_UP )
| | ( ( Thermostat [ ctr_output ] . status . controller_mode = = CTR_HYBRID )
& & ( Thermostat [ ctr_output ] . status . phase_hybrid_ctr = = CTR_HYBRID_RAMP_UP ) ) ) {
if ( Thermostat [ ctr_output ] . status . status_output = = IFACE_ON ) {
value = 100 ;
}
else {
value = 0 ;
}
}
ResponseCmndNumber ( ( int ) value ) ;
}
}
void CmndEnableOutputSet ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = THERMOSTAT_CONTROLLER_OUTPUTS ) ) {
uint8_t ctr_output = XdrvMailbox . index - 1 ;
if ( XdrvMailbox . data_len > 0 ) {
uint8_t value = ( uint8_t ) ( CharToFloat ( XdrvMailbox . data ) ) ;
if ( ( value > = IFACE_OFF ) & & ( value < = IFACE_ON ) ) {
Thermostat [ ctr_output ] . status . enable_output = value ;
}
}
2020-05-20 21:21:25 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . enable_output ) ;
2020-05-20 21:10:49 +01:00
}
}
2020-04-20 21:50:53 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv39 ( uint8_t function )
{
bool result = false ;
2020-05-02 21:33:29 +01:00
uint8_t ctr_output ;
2020-04-20 21:50:53 +01:00
switch ( function ) {
case FUNC_INIT :
2020-05-02 21:33:29 +01:00
for ( ctr_output = 0 ; ctr_output < THERMOSTAT_CONTROLLER_OUTPUTS ; ctr_output + + ) {
ThermostatInit ( ctr_output ) ;
}
2020-04-20 21:50:53 +01:00
break ;
case FUNC_LOOP :
2020-05-02 21:33:29 +01:00
for ( ctr_output = 0 ; ctr_output < THERMOSTAT_CONTROLLER_OUTPUTS ; ctr_output + + ) {
if ( Thermostat [ ctr_output ] . status . thermostat_mode ! = THERMOSTAT_OFF ) {
ThermostatSignalProcessingFast ( ctr_output ) ;
ThermostatDiagnostics ( ctr_output ) ;
}
2020-05-02 19:31:20 +01:00
}
2020-04-20 21:50:53 +01:00
break ;
case FUNC_SERIAL :
break ;
case FUNC_EVERY_SECOND :
2020-05-02 21:33:29 +01:00
for ( ctr_output = 0 ; ctr_output < THERMOSTAT_CONTROLLER_OUTPUTS ; ctr_output + + ) {
if ( ( ThermostatMinuteCounter ( ctr_output ) )
& & ( Thermostat [ ctr_output ] . status . thermostat_mode ! = THERMOSTAT_OFF ) ) {
ThermostatSignalPreProcessingSlow ( ctr_output ) ;
ThermostatController ( ctr_output ) ;
ThermostatSignalPostProcessingSlow ( ctr_output ) ;
2020-04-20 21:50:53 +01:00
# ifdef DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
ThermostatDebug ( ctr_output ) ;
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
}
2020-04-20 21:50:53 +01:00
}
break ;
2020-04-29 20:42:20 +01:00
case FUNC_SHOW_SENSOR :
2020-05-02 21:33:29 +01:00
for ( ctr_output = 0 ; ctr_output < THERMOSTAT_CONTROLLER_OUTPUTS ; ctr_output + + ) {
if ( Thermostat [ ctr_output ] . status . thermostat_mode ! = THERMOSTAT_OFF ) {
ThermostatGetLocalSensor ( ctr_output ) ;
}
2020-05-02 19:31:20 +01:00
}
2020-04-29 20:42:20 +01:00
break ;
2020-04-20 21:50:53 +01:00
case FUNC_COMMAND :
result = DecodeCommand ( kThermostatCommands , ThermostatCommand ) ;
break ;
}
return result ;
}
2020-05-05 19:40:09 +01:00
# endif // USE_THERMOSTAT