2020-04-20 21:50:53 +01:00
/*
2020-05-02 21:33:29 +01:00
xdrv_39_Thermostat [ ctr_output ] . 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-02 21:33:29 +01:00
# define DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
# ifdef DEBUG_THERMOSTAT
# define DOMOTICZ_IDX1 791
# define DOMOTICZ_IDX2 792
# define DOMOTICZ_IDX3 793
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"
# define D_CMND_TEMPFROSTPROTECTSET "TempFrostProtectSet"
# define D_CMND_CONTROLLERMODESET "ControllerModeSet"
# define D_CMND_INPUTSWITCHSET "InputSwitchSet"
# 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"
# 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-04-20 21:50:53 +01:00
enum ThermostatModes { THERMOSTAT_OFF , THERMOSTAT_AUTOMATIC_OP , THERMOSTAT_MANUAL_OP , THERMOSTAT_MODES_MAX } ;
enum ControllerModes { CTR_HYBRID , CTR_PI , CTR_RAMP_UP , CTR_MODES_MAX } ;
enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP , CTR_HYBRID_PI } ;
enum InterfaceStates { IFACE_OFF , IFACE_ON } ;
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 ,
THERMOSTAT_INPUT_SWT4
} ;
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
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-01 20:28:58 +01:00
uint32_t temp_format : 1 ; // Temperature format: Celsius/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-04-29 20:42:20 +01:00
uint32_t phase_hybrid_ctr : 1 ; // Phase of the hybrid controller (Ramp-up or PI)
uint32_t status_cycle_active : 1 ; // Status showing if cycle is active (Output ON) or not (Output OFF)
uint32_t state_emergency : 1 ; // State for thermostat emergency
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-02 19:31:20 +01:00
uint32_t output_inconsist_ctr : 2 ; // Counter of the minutes where there are inconsistent in the output state
uint32_t diagnostic_mode : 1 ; // Diagnostic mode selected
uint32_t free : 3 ; // Free bits in Bitfield
2020-04-20 21:50:53 +01:00
} ;
} ThermostatBitfield ;
# ifdef DEBUG_THERMOSTAT
const char DOMOTICZ_MES [ ] PROGMEM = " { \" idx \" :%d, \" nvalue \" :%d, \" svalue \" : \" %s \" } " ;
2020-04-29 20:42:20 +01:00
# endif // DEBUG_THERMOSTAT
2020-04-20 21:50:53 +01:00
const char kThermostatCommands [ ] PROGMEM = " | " D_CMND_THERMOSTATMODESET " | " D_CMND_TEMPFROSTPROTECTSET " | "
D_CMND_CONTROLLERMODESET " | " D_CMND_INPUTSWITCHSET " | " D_CMND_OUTPUTRELAYSET " | " D_CMND_TIMEALLOWRAMPUPSET " | "
2020-05-02 19:31:20 +01:00
D_CMND_TEMPFORMATSET " | " D_CMND_TEMPMEASUREDSET " | " D_CMND_TEMPTARGETSET " | " D_CMND_TEMPMEASUREDGRDREAD " | "
D_CMND_SENSORINPUTSET " | " D_CMND_STATEEMERGENCYSET " | " D_CMND_TIMEMANUALTOAUTOSET " | "
D_CMND_TIMEONLIMITSET " | " D_CMND_PROPBANDSET " | " D_CMND_TIMERESETSET " | " D_CMND_TIMEPICYCLESET " | "
D_CMND_TEMPANTIWINDUPRESETSET " | " D_CMND_TEMPHYSTSET " | " D_CMND_TIMEMAXACTIONSET " | " 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 ;
2020-04-20 21:50:53 +01:00
void ( * const ThermostatCommand [ ] ) ( void ) PROGMEM = {
& CmndThermostatModeSet , & CmndTempFrostProtectSet , & CmndControllerModeSet , & CmndInputSwitchSet , & CmndOutputRelaySet ,
2020-05-02 19:31:20 +01:00
& CmndTimeAllowRampupSet , & CmndTempFormatSet , & CmndTempMeasuredSet , & CmndTempTargetSet , & CmndTempMeasuredGrdRead ,
& CmndSensorInputSet , & CmndStateEmergencySet , & CmndTimeManualToAutoSet , & CmndTimeOnLimitSet ,
& CmndPropBandSet , & CmndTimeResetSet , & CmndTimePiCycleSet , & CmndTempAntiWindupResetSet , & CmndTempHystSet ,
& CmndTimeMaxActionSet , & CmndTimeMinActionSet , & CmndTimeMinTurnoffActionSet , & CmndTempRupDeltInSet ,
& CmndTempRupDeltOutSet , & CmndTimeRampupMaxSet , & CmndTimeRampupCycleSet , & CmndTempRampupPiAccErrSet ,
& CmndTimePiProportRead , & CmndTimePiIntegrRead , & CmndTimeSensLostSet , & CmndDiagnosticModeSet } ;
2020-04-20 21:50:53 +01:00
struct THERMOSTAT {
2020-04-29 20:42:20 +01:00
ThermostatBitfield 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
uint32_t time_ctr_checkpoint = 0 ; // Time to finalize the control cycle within the PI strategy or to switch to PI from Rampup
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
uint16_t kP_pi = 0 ; // kP value for the PI controller
uint16_t kI_pi = 0 ; // kP value for the PI controller multiplied by 100
int32_t temp_rampup_meas_gradient = 0 ; // Temperature measured gradient from sensor in thousandths of degrees per hour calculated during ramp-up
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-04-23 21:41:20 +01:00
int16_t temp_measured = 0 ; // Temperature measurement received from sensor in tenths of degrees
2020-04-29 20:42:20 +01:00
int16_t temp_rampup_output_off = 0 ; // Temperature to swith off relay output within the ramp-up controller in tenths of degrees
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-04-20 23:14:39 +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
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
uint16_t time_rampup_cycle = THERMOSTAT_TIME_RAMPUP_CYCLE ; // Time ramp-up cycle in seconds
uint16_t time_allow_rampup = THERMOSTAT_TIME_ALLOW_RAMPUP ; // Time in minutes after last target update to allow ramp-up controller phase
2020-04-20 21:50:53 +01:00
uint16_t time_sens_lost = THERMOSTAT_TIME_SENS_LOST ; // Maximum time w/o sensor update to set it as lost
uint16_t time_manual_to_auto = THERMOSTAT_TIME_MANUAL_TO_AUTO ; // Time without input switch active to change from manual to automatic in minutes
uint16_t time_on_limit = THERMOSTAT_TIME_ON_LIMIT ; // Maximum time with output active in minutes
uint16_t time_pi_cycle = THERMOSTAT_TIME_PI_CYCLE ; // Cycle time for the thermostat controller in seconds
2020-04-29 20:42:20 +01:00
uint32_t time_reset = THERMOSTAT_TIME_RESET ; // Reset time of the PI controller in seconds
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
uint16_t time_min_turnoff_action = THERMOSTAT_TIME_MIN_TURNOFF_ACTION ; // Minimum turnoff time in minutes, below it the thermostat will be held on
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-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 ;
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 ;
Thermostat [ ctr_output ] . status . state_emergency = EMERGENCY_OFF ;
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 ) ;
Thermostat [ ctr_output ] . status . output_inconsist_ctr = 0 ;
Thermostat [ ctr_output ] . status . diagnostic_mode = DIAGNOSTIC_ON ;
2020-04-29 20:42:20 +01:00
// Make sure the Output is OFF
2020-05-02 21:33:29 +01:00
ExecuteCommandPower ( Thermostat [ ctr_output ] . status . output_relay_number , POWER_OFF , SRC_THERMOSTAT ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +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-04-20 21:50:53 +01:00
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . status . counter_seconds % 60 ) = = 0 ) {
2020-04-20 21:50:53 +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
}
inline bool ThermostatSwitchIdValid ( uint8_t switchId )
{
return ( switchId > = THERMOSTAT_INPUT_SWT1 & & switchId < = THERMOSTAT_INPUT_SWT4 ) ;
}
inline bool ThermostatRelayIdValid ( uint8_t relayId )
{
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 )
{
return ( uint8_t ) bitRead ( power , ( output_switch - 1 ) ) ;
2020-04-20 21:50:53 +01:00
}
2020-05-01 20:28:58 +01:00
int16_t ThermostatCelsiusToFahrenheit ( const int32_t deg , uint8_t conv_type ) {
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 ;
}
int16_t ThermostatFahrenheitToCelsius ( const int32_t deg , uint8_t conv_type ) {
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-05-02 21:33:29 +01:00
if ( ( uptime - Thermostat [ ctr_output ] . timestamp_temp_measured_update ) > ( ( uint32_t ) Thermostat [ ctr_output ] . time_sens_lost * 60 ) ) {
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-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . status . status_output ! = Thermostat [ ctr_output ] . status . command_output ) {
Thermostat [ ctr_output ] . status . output_inconsist_ctr + + ;
2020-05-02 19:31:20 +01:00
}
else {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . status . 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 ) {
Thermostat [ ctr_output ] . timestamp_input_on = 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-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-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-02 21:33:29 +01:00
void ThermostatHybridCtrPhase ( uint8_t ctr_output )
2020-04-20 21:50:53 +01:00
{
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-04-20 21:50:53 +01:00
// If ramp-up offtime counter has been initalized
// AND ramp-up offtime counter value reached
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . time_ctr_checkpoint ! = 0 )
& & ( 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
// AND temp target - target actual bigger than threshold
// then go to ramp-up
2020-05-02 21:33:29 +01:00
if ( ( ( uptime - Thermostat [ ctr_output ] . timestamp_output_off ) > ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_allow_rampup ) )
& & ( Thermostat [ ctr_output ] . temp_target_level ! = Thermostat [ ctr_output ] . temp_target_level_ctr )
& & ( ( Thermostat [ ctr_output ] . temp_target_level - Thermostat [ ctr_output ] . temp_measured ) > Thermostat [ ctr_output ] . temp_rampup_delta_in ) ) {
Thermostat [ ctr_output ] . timestamp_rampup_start = uptime ;
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
}
break ;
}
}
# 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 ;
// If switch input is active
// OR temperature sensor is not alive
// then go to manual
2020-05-02 21:33:29 +01:00
if ( ( 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-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . status . status_input = = IFACE_OFF )
& & ( Thermostat [ ctr_output ] . status . sensor_alive = = IFACE_ON )
& & ( ( 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 ;
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-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
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
{
2020-05-02 21:33:29 +01:00
// TODO: See if the real output state can be read by f.i. bitRead(power, Thermostat[ctr_output].status.output_relay_number))
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-05-02 19:31:20 +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-02 21:33:29 +01:00
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-02 21:33:29 +01:00
ExecuteCommandPower ( Thermostat [ ctr_output ] . status . output_relay_number , POWER_OFF , SRC_THERMOSTAT ) ;
2020-05-02 19:31:20 +01:00
//#endif // DEBUG_THERMOSTAT
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . timestamp_output_off = uptime ;
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-04-26 16:59:36 +01:00
int32_t aux_time_error ;
2020-04-20 21:50:53 +01:00
// Calculate error
2020-05-02 21:33:29 +01:00
aux_time_error = ( int32_t ) ( Thermostat [ ctr_output ] . temp_target_level_ctr - Thermostat [ ctr_output ] . temp_measured ) * 10 ;
2020-04-26 16:59:36 +01:00
// Protect overflow
if ( aux_time_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
}
else if ( aux_time_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-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_error = ( int16_t ) aux_time_error ;
2020-04-26 16:59:36 +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-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . time_proportional_pi < 0 ) {
Thermostat [ ctr_output ] . time_proportional_pi = 0 ;
2020-04-20 21:50:53 +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)(((float)Thermostat[ctr_output].kP_pi * ((float)((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60) / (float)Thermostat[ctr_output].time_reset)) * 100);
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-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
// result will be calculated with the cummulated previous error anterior
// 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,
// there will be very strong control actions from the integral part due to a
// 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-02 21:33:29 +01:00
aux_time_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
if ( aux_time_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
}
else if ( aux_time_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-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_pi_accum_error = ( int16_t ) aux_time_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
// AND we are rising
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . temp_pi_error > = 0 )
& & ( abs ( ( Thermostat [ ctr_output ] . temp_pi_error ) / 10 ) < = ( int16_t ) Thermostat [ ctr_output ] . temp_hysteresis )
& & ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 ) ) {
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
// AND temperature is rising
2020-05-02 21:33:29 +01:00
else if ( ( Thermostat [ ctr_output ] . temp_pi_error < 0 )
& & ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 ) ) {
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
// 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-04-20 21:50:53 +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
// If target value has been reached or we are over it]]
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . temp_pi_error < = 0 ) {
2020-04-20 21:50:53 +01:00
// If we are over the hysteresis or the gradient is positive
2020-05-02 21:33:29 +01:00
if ( ( abs ( ( Thermostat [ ctr_output ] . temp_pi_error ) / 10 ) > Thermostat [ ctr_output ] . temp_hysteresis )
| | ( Thermostat [ ctr_output ] . temp_measured_gradient > = 0 ) ) {
Thermostat [ ctr_output ] . time_total_pi = 0 ;
2020-04-20 21:50:53 +01:00
}
}
// If target value has not been reached
2020-04-23 21:51:13 +01:00
// AND we are withinvr the histeresis
2020-04-20 21:50:53 +01:00
// AND gradient is positive
// 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 )
& & ( Thermostat [ ctr_output ] . temp_measured_gradient > 0 ) ) {
Thermostat [ ctr_output ] . time_total_pi = 0 ;
2020-04-20 21:50:53 +01:00
}
// Minimum action limiter
// 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
// 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
}
// Adjust output switch point
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_changepoint = uptime + ( uint32_t ) Thermostat [ ctr_output ] . time_total_pi ;
2020-04-20 21:50:53 +01:00
// Adjust next cycle point
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = 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
{
char result_chr [ FLOATSZ ] ; // Remove!
2020-05-02 21:33:29 +01:00
if ( ( uptime > = Thermostat [ ctr_output ] . time_ctr_checkpoint )
| | ( Thermostat [ ctr_output ] . temp_target_level ! = Thermostat [ ctr_output ] . temp_target_level_ctr )
| | ( ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_target_level )
& & ( Thermostat [ ctr_output ] . temp_measured_gradient < 0 )
& & ( 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-05-02 21:33:29 +01:00
if ( uptime < Thermostat [ ctr_output ] . time_ctr_changepoint ) {
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
{
2020-04-26 16:59:36 +01:00
int32_t aux_temp_delta ;
2020-04-20 21:50:53 +01:00
uint32_t time_in_rampup ;
int16_t temp_delta_rampup ;
// Update timestamp for temperature at start of ramp-up if temperature still dropping
2020-05-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_rampup_start ) {
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-05-02 21:33:29 +01:00
time_in_rampup = uptime - Thermostat [ ctr_output ] . timestamp_rampup_start ;
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
// AND temperature measured < target
2020-05-02 21:33:29 +01:00
if ( ( time_in_rampup < = ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) )
& & ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_target_level ) ) {
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-05-02 21:33:29 +01:00
if ( ( temp_delta_rampup > = Thermostat [ ctr_output ] . temp_rampup_delta_out )
& & ( 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
// minus open time of the valve (arround 3 minutes). If rise very fast limit it to delay of output valve
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
}
// Calculate 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 ) ;
Thermostat [ ctr_output ] . time_rampup_nextcycle = uptime + ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle ;
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 ;
Thermostat [ ctr_output ] . time_ctr_changepoint = uptime + ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) ;
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-05-02 21:33:29 +01:00
else if ( ( Thermostat [ ctr_output ] . time_rampup_deadtime > 0 ) & & ( 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 ;
uint32_t time_total_rampup = ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle * 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 ) ;
if ( Thermostat [ ctr_output ] . temp_rampup_meas_gradient > 0 ) {
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-04-29 12:46:22 +01:00
// x = ((y-y1)/(y2-y1))*(x2-x1) + x1 - deadtime
2020-05-02 21:33:29 +01:00
aux_temp_delta = ( int32_t ) ( Thermostat [ ctr_output ] . temp_target_level_ctr - Thermostat [ ctr_output ] . temp_rampup_cycle ) ;
2020-04-20 21:50:53 +01:00
2020-04-26 07:36:15 +01:00
// Protect overflow, if temperature goes down set max
if ( ( aux_temp_delta < 0 )
| | ( temp_delta_rampup < = 0 ) ) {
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_changepoint = uptime + ( uint32_t ) ( 60 * Thermostat [ ctr_output ] . time_rampup_max ) ;
2020-04-26 07:36:15 +01:00
}
else {
2020-05-02 21:33:29 +01:00
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-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . temp_rampup_output_off = ( int16_t ) ( ( ( int32_t ) temp_delta_rampup * ( int32_t ) ( Thermostat [ ctr_output ] . time_ctr_changepoint - ( 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-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_rampup_nextcycle = uptime + ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle ;
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-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_rampup_nextcycle = uptime + ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_cycle ;
2020-04-20 21:50:53 +01:00
// Reset time_ctr_changepoint and temp_rampup_output_off
2020-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_changepoint = uptime + ( 60 * ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) - time_in_rampup ;
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
// or gradient is <= 0
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . time_rampup_deadtime = = 0 )
| | ( Thermostat [ ctr_output ] . time_ctr_checkpoint = = 0 )
| | ( uptime < Thermostat [ ctr_output ] . time_ctr_changepoint )
| | ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_rampup_output_off )
| | ( Thermostat [ ctr_output ] . temp_rampup_meas_gradient < = 0 ) ) {
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-02 21:33:29 +01:00
if ( Thermostat [ ctr_output ] . temp_measured < Thermostat [ ctr_output ] . temp_target_level_ctr ) {
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-05-02 21:33:29 +01:00
Thermostat [ ctr_output ] . time_ctr_checkpoint = 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-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 ;
}
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-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-05-02 19:31:20 +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 ;
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-05-02 19:31:20 +01:00
{
// Diagnostic related to the plausibility of the output state
2020-05-02 21:33:29 +01:00
if ( ( Thermostat [ ctr_output ] . status . diagnostic_mode = = DIAGNOSTIC_ON )
& & ( Thermostat [ ctr_output ] . status . output_inconsist_ctr > = THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST ) ) {
Thermostat [ ctr_output ] . status . thermostat_mode = THERMOSTAT_OFF ;
Thermostat [ ctr_output ] . status . state_emergency = EMERGENCY_ON ;
2020-05-02 19:31:20 +01:00
}
// Diagnostic related to the plausibility of the output power implemented
// already into the energy driver
2020-05-02 21:33:29 +01:00
// If diagnostics fail, emergency enabled and thermostat shutdown triggered
if ( Thermostat [ ctr_output ] . status . state_emergency = = EMERGENCY_ON ) {
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
if ( ( tempVal > = - 1000 )
& & ( 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-02 21:33:29 +01:00
Response_P ( DOMOTICZ_MES , DOMOTICZ_IDX1 , ( 0 = = Thermostat [ ctr_output ] . status . command_output ) ? 0 : 1 , " " ) ;
2020-04-20 21:50:53 +01:00
MqttPublish ( domoticz_in_topic ) ;
}
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-02 21:33:29 +01:00
Response_P ( DOMOTICZ_MES , DOMOTICZ_IDX2 , ( 0 = = Thermostat [ ctr_output ] . status . phase_hybrid_ctr ) ? 0 : 1 , " " ) ;
2020-04-20 21:50:53 +01:00
MqttPublish ( domoticz_in_topic ) ;
2020-05-02 21:33:29 +01:00
//Response_P(DOMOTICZ_MES, DOMOTICZ_IDX3, (0 == Thermostat[ctr_output].time_ctr_changepoint) ? 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 ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . state_emergency , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.state_emergency: %s " ) , result_chr ) ;
dtostrfd ( Thermostat [ ctr_output ] . status . output_inconsist_ctr , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.output_inconsist_ctr: %s " ) , result_chr ) ;
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 ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " Thermostat[ctr_output].status.status_cycle_active: %s " ) , result_chr ) ;
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-05-02 19:31:20 +01:00
dtostrfd ( uptime , 0 , result_chr ) ;
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " uptime: %s " ) , result_chr ) ;
dtostrfd ( power , 0 , result_chr ) ;
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-04-29 20:42:20 +01:00
DynamicJsonBuffer jsonBuffer ;
JsonObject & root = jsonBuffer . parseObject ( ( const char * ) mqtt_data ) ;
if ( root . success ( ) ) {
2020-05-01 20:28:58 +01:00
const char * value_c = root [ " THERMOSTAT_SENSOR_NAME " ] [ " Temperature " ] ;
2020-04-29 20:42:20 +01:00
if ( value_c ! = NULL & & strlen ( value_c ) > 0 & & ( isdigit ( value_c [ 0 ] ) | | ( value_c [ 0 ] = = ' - ' & & isdigit ( value_c [ 1 ] ) ) ) ) {
int16_t value = ( int16_t ) ( CharToFloat ( value_c ) * 10 ) ;
if ( ( value > = - 1000 )
& & ( value < = 1000 )
2020-05-02 21:33:29 +01:00
& & ( Thermostat [ ctr_output ] . status . sensor_type = = SENSOR_LOCAL ) ) {
2020-04-29 20:42:20 +01:00
uint32_t timestamp = uptime ;
// 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
Thermostat [ ctr_output ] . temp_measured_gradient = ( int32_t ) ( ( 360000 * temp_delta ) / ( ( int32_t ) time_delta ) ) ; // hundreths of degrees per hour
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
}
}
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 ) ;
}
if ( ( value > = 0 )
& & ( 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-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 ;
Thermostat [ ctr_output ] . timestamp_input_on = uptime ;
}
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
}
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 ) ;
if ( ( value > = 0 ) & & ( value < 86400 ) ) {
Thermostat [ ctr_output ] . time_allow_rampup = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_allow_rampup * 60 ) ) ;
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 ) ;
}
if ( ( value > = - 1000 )
& & ( value < = 1000 )
& & ( Thermostat [ ctr_output ] . status . sensor_type = = SENSOR_MQTT ) ) {
uint32_t timestamp = uptime ;
// 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
Thermostat [ ctr_output ] . temp_measured_gradient = ( int32_t ) ( ( 360000 * temp_delta ) / ( ( int32_t ) time_delta ) ) ; // hundreths of degrees per hour
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 ) ;
}
if ( ( value > = - 1000 )
& & ( 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 ;
}
ResponseCmndFloat ( ( float ) value / 10 , 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 ) ) {
Thermostat [ ctr_output ] . status . state_emergency = ( uint16_t ) value ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . 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 ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_manual_to_auto = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_manual_to_auto * 60 ) ) ;
2020-04-20 21:50:53 +01:00
}
}
void CmndTimeOnLimitSet ( 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_on_limit = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_on_limit * 60 ) ) ;
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
}
}
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 ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_pi_cycle = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_pi_cycle * 60 ) ) ;
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 ) ;
}
if ( ( value > = 0 )
& & ( 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 ) ;
}
if ( ( value > = - 100 )
& & ( 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
}
}
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 ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_max_action = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_max_action * 60 ) ) ;
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 ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_min_action = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_min_action * 60 ) ) ;
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 ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_sens_lost = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_sens_lost * 60 ) ) ;
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 ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_min_turnoff_action = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( uint32_t ) Thermostat [ ctr_output ] . time_min_turnoff_action * 60 ) ) ;
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 ) ;
}
if ( ( value > = 0 )
& & ( 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 ) ;
}
if ( ( value > = 0 )
& & ( 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 ) ;
if ( ( value > = 0 ) & & ( value < = 86400 ) ) {
Thermostat [ ctr_output ] . time_rampup_max = ( uint16_t ) ( value / 60 ) ;
}
2020-04-20 21:50:53 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) ( ( ( uint32_t ) Thermostat [ ctr_output ] . time_rampup_max ) * 60 ) ) ;
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 ) ;
if ( ( value > = 0 ) & & ( value < = 54000 ) ) {
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 ) ;
}
if ( ( value > = 0 )
& & ( 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
}
}
void CmndTimePiProportRead ( 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 ;
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . time_proportional_pi ) ;
}
2020-04-20 21:50:53 +01:00
}
void CmndTimePiIntegrRead ( 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 ;
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . time_integral_pi ) ;
}
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 ) ) {
Thermostat [ ctr_output ] . status . diagnostic_mode = value ;
}
2020-05-02 19:31:20 +01:00
}
2020-05-02 21:33:29 +01:00
ResponseCmndNumber ( ( int ) Thermostat [ ctr_output ] . status . diagnostic_mode ) ;
2020-05-02 19:31:20 +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-02 19:32:37 +01:00
# endif // USE_THERMOSTAT