Merge pull request #79 from arendst/development

update
This commit is contained in:
Jason2866 2020-05-15 17:13:15 +02:00 committed by GitHub
commit 866b33c786
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 497 additions and 105 deletions

View File

@ -57,7 +57,7 @@ bool MutichannelGasSensor::isError()
unsigned char MutichannelGasSensor::getVersion()
{
if(get_addr_dta(CMD_READ_EEPROM, ADDR_IS_SET) == 1126) // get version
if(get_addr_dta(CMD_READ_EEPROM, ADDR_IS_SET) == 1126) // get version
{
__version = 2;
return __version;
@ -105,66 +105,66 @@ START:
return 0;
}
}
Wire.requestFrom(i2cAddress, (uint8_t)2);
unsigned int dta = 0;
unsigned char raw[10];
int cnt = 0;
while(Wire.available())
{
raw[cnt++] = Wire.read();
}
if(cnt == 0)goto START;
dta = raw[0];
dta <<= 8;
dta += raw[1];
switch(addr_reg)
{
case CH_VALUE_NH3:
if(dta > 0)
{
adcValueR0_NH3_Buf = dta;
}
else
else
{
dta = adcValueR0_NH3_Buf;
}
break;
case CH_VALUE_CO:
if(dta > 0)
{
adcValueR0_CO_Buf = dta;
}
else
else
{
dta = adcValueR0_CO_Buf;
}
break;
case CH_VALUE_NO2:
if(dta > 0)
{
adcValueR0_NO2_Buf = dta;
}
else
else
{
dta = adcValueR0_NO2_Buf;
}
break;
default:;
}
return dta;
@ -173,7 +173,7 @@ START:
unsigned int MutichannelGasSensor::get_addr_dta(unsigned char addr_reg, unsigned char __dta)
{
int trys = 0;
START:
START:
__send_error = false;
Wire.beginTransmission(i2cAddress);
Wire.write(addr_reg);
@ -185,25 +185,25 @@ START:
return 0;
}
}
Wire.requestFrom(i2cAddress, (uint8_t)2);
unsigned int dta = 0;
unsigned char raw[10];
int cnt = 0;
while(Wire.available())
{
raw[cnt++] = Wire.read();
}
if(cnt == 0)goto START;
dta = raw[0];
dta <<= 8;
dta += raw[1];
return dta;
}
@ -269,7 +269,7 @@ int16_t MutichannelGasSensor::readR0(void)
int16_t rtnData = 0;
rtnData = readData(0x11);
if(rtnData > 0)
res0[0] = rtnData;
else
@ -327,6 +327,30 @@ int16_t MutichannelGasSensor::readR(void)
** Returns:
float value - concentration of the gas
*********************************************************************************************************/
float MutichannelGasSensor_pow(float a, float b)
{
// https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/
// calculate approximation with fraction of the exponent
int e = abs((int)b);
union {
double d;
int x[2];
} u = { a };
u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447);
u.x[0] = 0;
// exponentiation by squaring with the exponent's integer part
// double r = u.d makes everything much slower, not sure why
double r = 1.0;
while (e) {
if (e & 1) {
r *= a;
}
a *= a;
e >>= 1;
}
return r * u.d;
}
float MutichannelGasSensor::calcGas(int gas)
{
@ -338,7 +362,7 @@ float MutichannelGasSensor::calcGas(int gas)
if(readR0() >= 0) r0_inited = true;
else return -1.0f;
}
if(readR() < 0)
return -2.0f;
@ -353,65 +377,65 @@ float MutichannelGasSensor::calcGas(int gas)
int A0_0 = get_addr_dta(6, ADDR_USER_ADC_HN3);
int A0_1 = get_addr_dta(6, ADDR_USER_ADC_CO);
int A0_2 = get_addr_dta(6, ADDR_USER_ADC_NO2);
int An_0 = get_addr_dta(CH_VALUE_NH3);
int An_1 = get_addr_dta(CH_VALUE_CO);
int An_2 = get_addr_dta(CH_VALUE_NO2);
ratio0 = (float)An_0/(float)A0_0*(1023.0-A0_0)/(1023.0-An_0);
ratio1 = (float)An_1/(float)A0_1*(1023.0-A0_1)/(1023.0-An_1);
ratio2 = (float)An_2/(float)A0_2*(1023.0-A0_2)/(1023.0-An_2);
}
float c = 0;
switch(gas)
{
case CO:
{
c = pow(ratio1, -1.179)*4.385; //mod by jack
c = MutichannelGasSensor_pow(ratio1, -1.179)*4.385; //mod by jack
break;
}
case NO2:
{
c = pow(ratio2, 1.007)/6.855; //mod by jack
c = MutichannelGasSensor_pow(ratio2, 1.007)/6.855; //mod by jack
break;
}
case NH3:
{
c = pow(ratio0, -1.67)/1.47; //modi by jack
c = MutichannelGasSensor_pow(ratio0, -1.67)/1.47; //modi by jack
break;
}
case C3H8: //add by jack
{
c = pow(ratio0, -2.518)*570.164;
c = MutichannelGasSensor_pow(ratio0, -2.518)*570.164;
break;
}
case C4H10: //add by jack
{
c = pow(ratio0, -2.138)*398.107;
c = MutichannelGasSensor_pow(ratio0, -2.138)*398.107;
break;
}
case GAS_CH4: //add by jack
{
c = pow(ratio1, -4.363)*630.957;
c = MutichannelGasSensor_pow(ratio1, -4.363)*630.957;
break;
}
case H2: //add by jack
{
c = pow(ratio1, -1.8)*0.73;
c = MutichannelGasSensor_pow(ratio1, -1.8)*0.73;
break;
}
case C2H5OH: //add by jack
{
c = pow(ratio1, -1.552)*1.622;
c = MutichannelGasSensor_pow(ratio1, -1.552)*1.622;
break;
}
default:
break;
}
if(2==__version)ledOff();
return isnan(c)?-3:c;
}
@ -474,7 +498,7 @@ void MutichannelGasSensor::doCalibrate(void)
a0 = get_addr_dta(CH_VALUE_NH3);
a1 = get_addr_dta(CH_VALUE_CO);
a2 = get_addr_dta(CH_VALUE_NO2);
Serial.print(a0);
Serial.print('\t');
Serial.print(a1);
@ -482,44 +506,44 @@ void MutichannelGasSensor::doCalibrate(void)
Serial.print(a2);
Serial.println('\t');
ledOn();
int cnt = 0;
for(i=0; i<20; i++)
{
if((a0 - get_addr_dta(CH_VALUE_NH3)) > 2 || (get_addr_dta(CH_VALUE_NH3) - a0) > 2)cnt++;
if((a1 - get_addr_dta(CH_VALUE_CO)) > 2 || (get_addr_dta(CH_VALUE_CO) - a1) > 2)cnt++;
if((a2 - get_addr_dta(CH_VALUE_NO2)) > 2 || (get_addr_dta(CH_VALUE_NO2) - a2) > 2)cnt++;
if(cnt>5)
{
break;
}
delay(1000);
}
ledOff();
if(cnt <= 5)break;
delay(200);
}
Serial.print("write user adc value: ");
Serial.print(a0);Serial.print('\t');
Serial.print(a1);Serial.print('\t');
Serial.print(a2);Serial.println('\t');
unsigned char tmp[7];
tmp[0] = 7;
tmp[1] = a0>>8;
tmp[2] = a0&0xff;
tmp[3] = a1>>8;
tmp[4] = a1&0xff;
tmp[5] = a2>>8;
tmp[6] = a2&0xff;
write_i2c(i2cAddress, tmp, 7);
}
}
@ -563,7 +587,7 @@ void MutichannelGasSensor::display_eeprom()
Serial.println("ERROR: display_eeprom() is NOT support by V1 firmware.");
return ;
}
Serial.print("ADDR_IS_SET = "); Serial.println(get_addr_dta(CMD_READ_EEPROM, ADDR_IS_SET));
Serial.print("ADDR_FACTORY_ADC_NH3 = "); Serial.println(get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_NH3));
Serial.print("ADDR_FACTORY_ADC_CO = "); Serial.println(get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_CO));
@ -581,7 +605,7 @@ float MutichannelGasSensor::getR0(unsigned char ch) // 0:CH3, 1:CO, 2:NO
Serial.println("ERROR: getR0() is NOT support by V1 firmware.");
return -1;
}
int a = 0;
switch(ch)
{
@ -590,19 +614,19 @@ float MutichannelGasSensor::getR0(unsigned char ch) // 0:CH3, 1:CO, 2:NO
Serial.print("a_ch3 = ");
Serial.println(a);
break;
case 1: // CO
a = get_addr_dta(CMD_READ_EEPROM, ADDR_USER_ADC_CO);
Serial.print("a_co = ");
Serial.println(a);
break;
case 2: // NO2
a = get_addr_dta(CMD_READ_EEPROM, ADDR_USER_ADC_NO2);
Serial.print("a_no2 = ");
Serial.println(a);
break;
default:;
}
@ -612,31 +636,31 @@ float MutichannelGasSensor::getR0(unsigned char ch) // 0:CH3, 1:CO, 2:NO
float MutichannelGasSensor::getRs(unsigned char ch) // 0:CH3, 1:CO, 2:NO2
{
if(__version == 1)
{
Serial.println("ERROR: getRs() is NOT support by V1 firmware.");
return -1;
}
int a = 0;
switch(ch)
{
case 0: // NH3
a = get_addr_dta(1);
break;
case 1: // CO
a = get_addr_dta(2);
break;
case 2: // NO2
a = get_addr_dta(3);
break;
default:;
}
float r = 56.0*(float)a/(1023.0-(float)a);
return r;
}
@ -645,12 +669,12 @@ float MutichannelGasSensor::getRs(unsigned char ch) // 0:CH3, 1:CO, 2:NO
// 2. change adc value of R0 to default
void MutichannelGasSensor::factory_setting()
{
unsigned char tmp[7];
unsigned char error;
unsigned char address = 0;
for(address = 1; address < 127; address++ )
{
// The i2c_scanner uses the return value of
@ -661,11 +685,11 @@ void MutichannelGasSensor::factory_setting()
if (error == 0)
{
// change i2c to 0x04
Serial.print("I2C address is: 0x");
Serial.println(address, HEX);
Serial.println("Change I2C address to 0x04");
dta_test[0] = CMD_CHANGE_I2C;
dta_test[1] = 0x04;
write_i2c(address, dta_test, 2);
@ -680,15 +704,15 @@ void MutichannelGasSensor::factory_setting()
unsigned int a0 = get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_NH3);
unsigned int a1 = get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_CO);
unsigned int a2 = get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_NO2);
tmp[0] = 7;
tmp[1] = a0>>8;
tmp[2] = a0&0xff;
tmp[2] = a0&0xff;
tmp[3] = a1>>8;
tmp[4] = a1&0xff;
tmp[5] = a2>>8;
tmp[6] = a2&0xff;
tmp[6] = a2&0xff;
delay(100);
write_i2c(i2cAddress, tmp, 7);
delay(100);
@ -699,13 +723,13 @@ void MutichannelGasSensor::change_i2c_address(unsigned char addr)
dta_test[0] = CMD_CHANGE_I2C;
dta_test[1] = addr;
write_i2c(i2cAddress, dta_test, 2);
Serial.print("FUNCTION: CHANGE I2C ADDRESS: 0X");
Serial.print(i2cAddress, HEX);
Serial.print(" > 0x");
Serial.println(addr, HEX);
i2cAddress = addr;
}

View File

@ -35,12 +35,36 @@ uint16_t ESPKNXIP::data_to_2byte_uint(uint8_t *data)
return (uint16_t)((data[1] << 8) | data[2]);
}
float esp_knx_pow(float a, float b)
{
// https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/
// calculate approximation with fraction of the exponent
int e = abs((int)b);
union {
double d;
int x[2];
} u = { a };
u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447);
u.x[0] = 0;
// exponentiation by squaring with the exponent's integer part
// double r = u.d makes everything much slower, not sure why
double r = 1.0;
while (e) {
if (e & 1) {
r *= a;
}
a *= a;
e >>= 1;
}
return r * u.d;
}
float ESPKNXIP::data_to_2byte_float(uint8_t *data)
{
//uint8_t sign = (data[1] & 0b10000000) >> 7;
uint8_t expo = (data[1] & 0b01111000) >> 3;
int16_t mant = ((data[1] & 0b10000111) << 8) | data[2];
return 0.01f * mant * pow(2, expo);
return 0.01f * mant * esp_knx_pow(2, expo);
}
time_of_day_t ESPKNXIP::data_to_3byte_time(uint8_t *data)

View File

@ -699,6 +699,11 @@
#define THERMOSTAT_TIME_OUTPUT_DELAY 180 // Default output delay between state change and real actuation event (f.i. valve open/closed)
#define THERMOSTAT_TEMP_INIT 180 // Default init target temperature for the thermostat controller
#define THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST 3 // Default maximum time where the input and the outpus shall differ (for diagnostic) in minutes
#define THERMOSTAT_TIME_MAX_AUTOTUNE 21600 // Maximum time for the PI autotune function to complete in seconds
#define THERMOSTAT_DUTYCYCLE_AUTOTUNE 35 // Default duty cycle (in % over PI cycle time) for the step response of the autotune PI function
#define THERMOSTAT_PEAKNUMBER_AUTOTUNE 8 // Default number of peak temperatures (max or min) to be used for the autotune PI function
#define THERMOSTAT_TEMP_BAND_NO_PEAK_DET 1 // Default temperature band in thenths of degrees celsius within no peak will be detected
#define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful
// -- End of general directives -------------------

View File

@ -24,6 +24,10 @@
// Enable/disable debugging
//#define DEBUG_THERMOSTAT
// Enable/disable experimental PI auto-tuning inspired by the Arduino Autotune Library by
// Brett Beauregard <br3ttb@gmail.com> brettbeauregard.com
//#define USE_PI_AUTOTUNING // (Ziegler-Nichols closed loop method)
#ifdef DEBUG_THERMOSTAT
#define DOMOTICZ_MAX_IDX 4
#define DOMOTICZ_IDX1 791
@ -56,6 +60,9 @@
#define D_CMND_TIMEPICYCLESET "TimePiCycleSet"
#define D_CMND_TEMPANTIWINDUPRESETSET "TempAntiWindupResetSet"
#define D_CMND_TEMPHYSTSET "TempHystSet"
#ifdef USE_PI_AUTOTUNING
#define D_CMND_PERFLEVELAUTOTUNE "PerfLevelAutotune"
#endif // USE_PI_AUTOTUNING
#define D_CMND_TIMEMAXACTIONSET "TimeMaxActionSet"
#define D_CMND_TIMEMINACTIONSET "TimeMinActionSet"
#define D_CMND_TIMEMINTURNOFFACTIONSET "TimeMinTurnoffActionSet"
@ -70,8 +77,15 @@
#define D_CMND_DIAGNOSTICMODESET "DiagnosticModeSet"
enum ThermostatModes { THERMOSTAT_OFF, THERMOSTAT_AUTOMATIC_OP, THERMOSTAT_MANUAL_OP, THERMOSTAT_MODES_MAX };
#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
enum ControllerModes { CTR_HYBRID, CTR_PI, CTR_RAMP_UP, CTR_MODES_MAX };
enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP, CTR_HYBRID_PI };
#endif // USE_PI_AUTOTUNING
enum ClimateModes { CLIMATE_HEATING, CLIMATE_COOLING, CLIMATE_MODES_MAX };
enum InterfaceStates { IFACE_OFF, IFACE_ON };
enum InputUsage { INPUT_NOT_USED, INPUT_USED };
@ -113,17 +127,29 @@ typedef union {
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)
uint32_t use_input : 1; // Flag stating if the input switch shall be used to switch to manual mode
uint32_t phase_hybrid_ctr : 1; // Phase of the hybrid controller (Ramp-up or PI)
uint32_t phase_hybrid_ctr : 2; // Phase of the hybrid controller (Ramp-up, PI or Autotune)
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
uint32_t output_inconsist_ctr : 2; // Counter of the minutes where the output state is inconsistent with the command
uint32_t diagnostic_mode : 1; // Diagnostic mode selected
uint32_t free : 1; // Free bits in Bitfield
#ifdef USE_PI_AUTOTUNING
uint32_t autotune_flag : 1; // Enable/disable autotune
uint32_t autotune_perf_mode : 2; // Autotune performance mode
uint32_t free : 1; // Free bits
#else
uint32_t free : 4; // Free bits
#endif // USE_PI_AUTOTUNING
};
} ThermostatBitfield;
} 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
};
} ThermostatDiagBitfield;
#ifdef DEBUG_THERMOSTAT
const char DOMOTICZ_MES[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\"}";
@ -135,28 +161,36 @@ const char kThermostatCommands[] PROGMEM = "|" D_CMND_THERMOSTATMODESET "|" D_CM
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 "|"
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;
#ifdef USE_PI_AUTOTUNING
D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_PERFLEVELAUTOTUNE "|" D_CMND_TIMEMAXACTIONSET "|"
#else
D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|"
#endif // USE_PI_AUTOTUNING
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;
void (* const ThermostatCommand[])(void) PROGMEM = {
&CmndThermostatModeSet, &CmndClimateModeSet, &CmndTempFrostProtectSet, &CmndControllerModeSet, &CmndInputSwitchSet,
&CmndInputSwitchUse, &CmndOutputRelaySet, &CmndTimeAllowRampupSet, &CmndTempFormatSet, &CmndTempMeasuredSet,
&CmndTempTargetSet, &CmndTempMeasuredGrdRead, &CmndSensorInputSet, &CmndStateEmergencySet, &CmndTimeManualToAutoSet,
&CmndPropBandSet, &CmndTimeResetSet, &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, &CmndTempHystSet,
#ifdef USE_PI_AUTOTUNING
&CmndPerfLevelAutotune, &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet,
#else
&CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet,
#endif // USE_PI_AUTOTUNING
&CmndTempRupDeltOutSet, &CmndTimeRampupMaxSet, &CmndTimeRampupCycleSet, &CmndTempRampupPiAccErrSet,
&CmndTimePiProportRead, &CmndTimePiIntegrRead, &CmndTimeSensLostSet, &CmndDiagnosticModeSet };
struct THERMOSTAT {
ThermostatBitfield status; // Bittfield including states as well as several flags
ThermostatStateBitfield status; // Bittfield including states as well as several flags
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
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
uint32_t time_ctr_changepoint = 0; // Time until switching off output within the controller in seconds
int32_t temp_measured_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees per hour
int16_t temp_target_level = THERMOSTAT_TEMP_INIT; // Target level of the thermostat in tenths of degrees
@ -167,7 +201,7 @@ struct THERMOSTAT {
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 multiplied by 100 (to avoid floating point operations)
uint16_t kI_pi = 0; // kP value for the PI controller multiplied by 100 (to avoid floating point operations)
uint16_t kI_pi = 0; // kI value for the PI controller multiplied by 100 (to avoid floating point operations)
int32_t temp_rampup_meas_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees celsius 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)
@ -195,6 +229,23 @@ struct THERMOSTAT {
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
uint8_t temp_frost_protect = THERMOSTAT_TEMP_FROST_PROTECT; // Minimum temperature for frost protection, in tenths of degrees celsius
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
} Thermostat[THERMOSTAT_CONTROLLER_OUTPUTS];
/*********************************************************************************************/
@ -212,13 +263,17 @@ void ThermostatInit(uint8_t ctr_output)
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].diag.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.use_input = INPUT_NOT_USED;
Thermostat[ctr_output].status.output_inconsist_ctr = 0;
Thermostat[ctr_output].status.diagnostic_mode = DIAGNOSTIC_ON;
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
// Make sure the Output is OFF
ExecuteCommandPower(Thermostat[ctr_output].status.output_relay_number, POWER_OFF, SRC_THERMOSTAT);
}
@ -312,10 +367,10 @@ void ThermostatSignalPostProcessingSlow(uint8_t ctr_output)
{
// Increate counter when inconsistent output state exists
if (Thermostat[ctr_output].status.status_output != Thermostat[ctr_output].status.command_output) {
Thermostat[ctr_output].status.output_inconsist_ctr++;
Thermostat[ctr_output].diag.output_inconsist_ctr++;
}
else {
Thermostat[ctr_output].status.output_inconsist_ctr = 0;
Thermostat[ctr_output].diag.output_inconsist_ctr = 0;
}
}
@ -333,6 +388,10 @@ void ThermostatSignalProcessingFast(uint8_t ctr_output)
void ThermostatCtrState(uint8_t ctr_output)
{
#ifdef USE_PI_AUTOTUNING
bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING);
#endif //USE_PI_AUTOTUNING
switch (Thermostat[ctr_output].status.controller_mode) {
// Hybrid controller (Ramp-up + PI)
case CTR_HYBRID:
@ -340,10 +399,35 @@ void ThermostatCtrState(uint8_t ctr_output)
break;
// PI controller
case CTR_PI:
#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
break;
// Ramp-up controller (predictive)
case CTR_RAMP_UP:
break;
#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
}
}
@ -387,7 +471,32 @@ void ThermostatHybridCtrPhase(uint8_t ctr_output)
Thermostat[ctr_output].time_ctr_checkpoint = 0;
Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_RAMP_UP;
}
#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
break;
#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;
}
break;
#endif // USE_PI_AUTOTUNING
}
}
#ifdef DEBUG_THERMOSTAT
@ -830,6 +939,198 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output)
}
}
#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;
Thermostat[ctr_output].kU_pi_atune = 0;
Thermostat[ctr_output].peak_ctr = 0;
Thermostat[ctr_output].temp_abs_max_atune = 0;
Thermostat[ctr_output].temp_abs_min_atune = 100;
Thermostat[ctr_output].time_ctr_checkpoint = uptime + THERMOSTAT_TIME_MAX_AUTOTUNE;
}
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;
}
// 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;
Thermostat[ctr_output].time_peak_timestamps_atune[peak_num] = (uptime / 60);
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)) {
// Calculate period
// Register peak timestamp;
Thermostat[ctr_output].time_peak_timestamps_atune[peak_num] = (uptime / 60);
Thermostat[ctr_output].peak_ctr++;
peak_transition = true;
}
}
}
else {
// Peak detection done, proceed to evaluate results
ThermostatAutotuneParamCalc(ctr_output);
Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF;
}
// 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
peak_avg = (abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 1]
- Thermostat[ctr_output].temp_peaks_atune[peak_num - 2])
+ abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 2]
- Thermostat[ctr_output].temp_peaks_atune[peak_num - 3])) / 2;
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);
Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF;
}
}
}
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]);
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
// AND no change in setpoint
if ((uptime < Thermostat[ctr_output].time_ctr_checkpoint)
&&(Thermostat[ctr_output].temp_target_level_ctr == Thermostat[ctr_output].temp_target_level)) {
if (uptime >= Thermostat[ctr_output].time_ctr_checkpoint) {
Thermostat[ctr_output].temp_target_level_ctr = Thermostat[ctr_output].temp_target_level;
// Calculate time_ctr_changepoint
Thermostat[ctr_output].time_ctr_changepoint = uptime + (((uint32_t)Thermostat[ctr_output].time_pi_cycle * (uint32_t)Thermostat[ctr_output].dutycycle_step_autotune) / (uint32_t)100);
// Reset cycle active
Thermostat[ctr_output].status.status_cycle_active = CYCLE_OFF;
}
// Set Output On/Off depending on the changepoint
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;
}
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
void ThermostatCtrWork(uint8_t ctr_output)
{
switch (Thermostat[ctr_output].status.controller_mode) {
@ -842,6 +1143,12 @@ void ThermostatCtrWork(uint8_t ctr_output)
case CTR_HYBRID_PI:
ThermostatWorkAutomaticPI(ctr_output);
break;
#ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_HYBRID_PI_AUTOTUNE:
ThermostatWorkAutomaticPIAutotune(ctr_output);
break;
#endif //USE_PI_AUTOTUNING
}
break;
// PI controller
@ -852,6 +1159,12 @@ void ThermostatCtrWork(uint8_t ctr_output)
case CTR_RAMP_UP:
ThermostatWorkAutomaticRampUp(ctr_output);
break;
#ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_PI_AUTOTUNE:
ThermostatWorkAutomaticPIAutotune(ctr_output);
break;
#endif //USE_PI_AUTOTUNING
}
}
@ -879,17 +1192,17 @@ void ThermostatWork(uint8_t ctr_output)
void ThermostatDiagnostics(uint8_t ctr_output)
{
// Diagnostic related to the plausibility of the output state
if ((Thermostat[ctr_output].status.diagnostic_mode == DIAGNOSTIC_ON)
&&(Thermostat[ctr_output].status.output_inconsist_ctr >= THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST)) {
if ((Thermostat[ctr_output].diag.diagnostic_mode == DIAGNOSTIC_ON)
&&(Thermostat[ctr_output].diag.output_inconsist_ctr >= THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST)) {
Thermostat[ctr_output].status.thermostat_mode = THERMOSTAT_OFF;
Thermostat[ctr_output].status.state_emergency = EMERGENCY_ON;
Thermostat[ctr_output].diag.state_emergency = EMERGENCY_ON;
}
// Diagnostic related to the plausibility of the output power implemented
// already into the energy driver
// If diagnostics fail, emergency enabled and thermostat shutdown triggered
if (Thermostat[ctr_output].status.state_emergency == EMERGENCY_ON) {
if (Thermostat[ctr_output].diag.state_emergency == EMERGENCY_ON) {
ThermostatEmergencyShutdown(ctr_output);
}
}
@ -947,10 +1260,10 @@ void ThermostatDebug(uint8_t ctr_output)
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].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);
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);
@ -1049,6 +1362,8 @@ void CmndClimateModeSet(void)
uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data));
if ((value >= CLIMATE_HEATING) && (value < CLIMATE_MODES_MAX)) {
Thermostat[ctr_output].status.climate_mode = value;
// Trigger a restart of the controller
Thermostat[ctr_output].time_ctr_checkpoint = uptime;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.climate_mode);
@ -1090,6 +1405,14 @@ void CmndControllerModeSet(void)
uint8_t value = (uint8_t)(XdrvMailbox.payload);
if ((value >= CTR_HYBRID) && (value < CTR_MODES_MAX)) {
Thermostat[ctr_output].status.controller_mode = value;
// Reset controller variables
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;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.controller_mode);
@ -1156,11 +1479,11 @@ void CmndTimeAllowRampupSet(void)
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);
if ((value >= 0) && (value < 1440)) {
Thermostat[ctr_output].time_allow_rampup = (uint16_t)value;
}
}
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_allow_rampup * 60));
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_allow_rampup));
}
}
@ -1266,10 +1589,10 @@ void CmndStateEmergencySet(void)
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;
Thermostat[ctr_output].diag.state_emergency = (uint16_t)value;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.state_emergency);
ResponseCmndNumber((int)Thermostat[ctr_output].diag.state_emergency);
}
}
@ -1399,6 +1722,22 @@ void CmndTempHystSet(void)
}
}
#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
void CmndTimeMaxActionSet(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
@ -1571,10 +1910,10 @@ void CmndDiagnosticModeSet(void)
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;
Thermostat[ctr_output].diag.diagnostic_mode = value;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.diagnostic_mode);
ResponseCmndNumber((int)Thermostat[ctr_output].diag.diagnostic_mode);
}
}