From 31d0268df4ac1b7f8baa5f12704e15c6c0ee9c6d Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Fri, 17 Apr 2020 10:31:53 +0200 Subject: [PATCH 01/16] Heating controller added. Created by myself initially in LUA running as a Domoticz (running on a raspberry pi) script and controlling Qubino relays for floor heating. Ported to tasmota to get the logic within the relay itself and be less dependent on Domoticz. The controller supports several working modes. From off (no action) to manual (following input), automatic (hybrid, rampup or pi controller) and timeplan (automatic following predefined schedule with 3 temperatures for each weekday). It is fully configured via commands, it will include in the future diagnostics and will be extended to more outputs (will be tested on sonoff 4CH Pro). The controller has been tested successfully with a Shelly 1PM device and works as the original LUA domoticz script. --- tasmota/i18n.h | 34 ++++++++++++++++++++++++++++++++++ tasmota/my_user_config.h | 32 ++++++++++++++++++++++++++++++++ tasmota/settings.h | 30 +++++++++++++++++++++++++++++- tasmota/settings.ino | 28 ++++++++++++++++++++++++++++ tasmota/support_features.ino | 5 +++-- tasmota/tasmota.h | 2 +- 6 files changed, 127 insertions(+), 4 deletions(-) diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 9f99d7c1d..2b5487a57 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -577,6 +577,40 @@ #define D_CMND_PING "Ping" #define D_JSON_PING "Ping" +// Commands xdrv_90_heating.ino +#define D_CMND_HEATINGMODESET "HeatingModeSet" +#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" +#define D_CMND_TEMPMEASUREDSET "TempMeasuredSet" +#define D_CMND_TEMPTARGETSET "TempTargetSet" +#define D_CMND_TIMEPLANSET "TimePlanSet" +#define D_CMND_TEMPTARGETREAD "TempTargetRead" +#define D_CMND_TEMPMEASUREDREAD "TempMeasuredRead" +#define D_CMND_TEMPMEASUREDGRDREAD "TempMeasuredGrdRead" +#define D_CMND_TEMPSENSNUMBERSET "TempSensNumberSet" +#define D_CMND_STATEEMERGENCYSET "StateEmergencySet" +#define D_CMND_POWERMAXSET "PowerMaxSet" +#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" + // Commands xsns_02_analog.ino #define D_CMND_ADCPARAM "AdcParam" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 6ac041770..a1899a20f 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -656,6 +656,38 @@ #define USE_TASMOTA_SLAVE_FLASH_SPEED 57600 // Usually 57600 for 3.3V variants and 115200 for 5V variants #define USE_TASMOTA_SLAVE_SERIAL_SPEED 57600 // Depends on the sketch that is running on the Uno/Pro Mini +/*********************************************************************************************\ + * HEATING CONTROLLER +\*********************************************************************************************/ + +#define USE_HEATING + +#define HEATING_RELAY_NUMBER 1 // Default output relay number +#define HEATING_SWITCH_NUMBER 1 // Default input switch number +#define HEATING_TIME_ALLOW_RAMPUP 18000 // Default time in seconds after last target update to allow ramp-up controller phase +#define HEATING_TIME_RAMPUP_MAX 57600 // Default time maximum ramp-up controller duration +#define HEATING_TIME_RAMPUP_CYCLE 1800 // Default time ramp-up cycle +#define HEAT_TIME_SENS_LOST 1800 // Default target temperature in seconds +#define HEAT_TEMP_SENS_NUMBER 1 // Default temperature sensor number +#define HEAT_STATE_EMERGENCY false // Default state for heating emergency +#define HEAT_POWER_MAX 60 // Default maximum output power in Watt +#define HEAT_TIME_MANUAL_TO_AUTO 3600 // Default time without input switch active to change from manual to automatic in seconds +#define HEAT_TIME_ON_LIMIT 7200 // Default maximum time with output active in seconds +#define HEAT_TIME_RESET 12000 // Default reset time of the PI controller in seconds +#define HEAT_TIME_PI_CYCLE 1800 // Default cycle time for the heating controller in seconds +#define HEAT_TIME_MAX_ACTION 1200 // Default maximum heating time per cycle in seconds +#define HEAT_TIME_MIN_ACTION 240 // Default minimum heating time per cycle in seconds +#define HEAT_TIME_MIN_TURNOFF_ACTION 180 // Default minimum turnoff time in seconds, below it the heating will be held on +#define HEAT_PROP_BAND 4 // Default proportional band of the PI controller in degrees celsius +#define HEAT_TEMP_RESET_ANTI_WINDUP 8 // Default range where reset antiwindup is disabled, in tenths of degrees celsius +#define HEAT_TEMP_HYSTERESIS 1 // Default range hysteresis for temperature PI controller, in tenths of degrees celsius +#define HEAT_TEMP_FROST_PROTECT 40 // Default minimum temperature for frost protection, in tenths of degrees celsius +#define HEATING_TEMP_RAMPUP_DELTA_IN 4 // Default minimum delta temperature to target to get into rampup mode, in tenths of degrees celsius +#define HEATING_TEMP_RAMPUP_DELTA_OUT 2 // Default minimum delta temperature to target to get out of the rampup mode, in tenths of degrees celsius +#define HEATING_TEMP_PI_RAMPUP_ACC_E 20 // Default accumulated error when switching from ramp-up controller to PI +#define HEATING_ENERGY_OUTPUT_MAX 10 // Default maximum allowed energy output for heating valve in Watts +#define HEATING_TIME_OUTPUT_DELAY 180 // Default output delay between state change and real actuation event (f.i. valve open/closed) + // -- End of general directives ------------------- /*********************************************************************************************\ diff --git a/tasmota/settings.h b/tasmota/settings.h index 01f0cf174..6d7962723 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -516,6 +516,7 @@ struct SYSCFG { uint8_t wifi_bssid[6]; // F0A uint8_t as3935_sensor_cfg[5]; // F10 As3935IntCfg as3935_functions; // F15 + //uint8_t free_f35; As3935Param as3935_parameter; // F16 uint64_t zb_ext_panid; // F18 uint64_t zb_precfgkey_l; // F20 @@ -525,11 +526,38 @@ struct SYSCFG { uint8_t zb_free_byte; // F33 uint16_t pms_wake_interval; // F34 - uint8_t free_f36[130]; // F36 - Decrement if adding new Setting variables just above and below + uint8_t free_f36[70]; // F36 - Decrement if adding new Setting variables just above and below // Only 32 bit boundary variables below + uint8_t time_output_delay; // F7C + uint8_t temp_rampup_pi_acc_error; // F7D + uint8_t temp_rampup_delta_out; // F7E + uint8_t temp_rampup_delta_in; // F7F + uint32_t time_rampup_max; // F80 + uint32_t time_rampup_cycle; // F84 + uint32_t time_allow_rampup; // F88 + uint32_t time_sens_lost; // F8C + uint8_t temp_sens_number; // F90 + bool state_emergency; // F91 + uint8_t output_relay_number; // F92 + uint8_t input_switch_number; // F93 + uint32_t time_manual_to_auto; // F94 + uint32_t time_on_limit; // F98 + uint32_t time_reset; // F9C + uint32_t time_pi_cycle; // FA0 + uint32_t time_max_action; // FA4 + uint32_t time_min_action; // FA8 + uint32_t time_min_turnoff_action; // FAC + uint8_t val_prop_band; // FB0 + uint8_t temp_reset_anti_windup; // FB1 + int8_t temp_hysteresis; // FB2 + uint8_t temp_frost_protect; // FB3 + uint16_t power_max; // FB4 + uint16_t energy_heating_output_max; // FB6 + uint16_t pulse_counter_debounce_low; // FB8 uint16_t pulse_counter_debounce_high; // FBA + uint32_t keeloq_master_msb; // FBC uint32_t keeloq_master_lsb; // FC0 uint32_t keeloq_serial; // FC4 diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 53110155f..ed2dd021f 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -1001,6 +1001,34 @@ void SettingsDefaultSet2(void) Settings.flag3.shutter_mode = SHUTTER_SUPPORT; Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS; Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES; + + // Heating + Settings.energy_heating_output_max = HEATING_ENERGY_OUTPUT_MAX; + Settings.time_output_delay = HEATING_TIME_OUTPUT_DELAY; + Settings.temp_rampup_pi_acc_error = HEATING_TEMP_PI_RAMPUP_ACC_E; + Settings.temp_rampup_delta_out = HEATING_TEMP_RAMPUP_DELTA_OUT; + Settings.temp_rampup_delta_in = HEATING_TEMP_RAMPUP_DELTA_IN; + Settings.output_relay_number = HEATING_RELAY_NUMBER; + Settings.input_switch_number = HEATING_SWITCH_NUMBER; + Settings.time_allow_rampup = HEATING_TIME_ALLOW_RAMPUP; + Settings.time_rampup_max = HEATING_TIME_RAMPUP_MAX; + Settings.time_rampup_cycle = HEATING_TIME_RAMPUP_CYCLE; + Settings.time_sens_lost = HEAT_TIME_SENS_LOST; + Settings.temp_sens_number = HEAT_TEMP_SENS_NUMBER; + Settings.state_emergency = HEAT_STATE_EMERGENCY; + Settings.power_max = HEAT_POWER_MAX; + Settings.time_manual_to_auto = HEAT_TIME_MANUAL_TO_AUTO; + Settings.time_on_limit = HEAT_TIME_ON_LIMIT; + Settings.time_reset = HEAT_TIME_RESET; + Settings.time_pi_cycle = HEAT_TIME_PI_CYCLE; + Settings.time_max_action = HEAT_TIME_MAX_ACTION; + Settings.time_min_action = HEAT_TIME_MIN_ACTION; + Settings.time_min_turnoff_action = HEAT_TIME_MIN_TURNOFF_ACTION; + Settings.val_prop_band = HEAT_PROP_BAND; + Settings.temp_reset_anti_windup = HEAT_TEMP_RESET_ANTI_WINDUP; + Settings.temp_hysteresis = HEAT_TEMP_HYSTERESIS; + Settings.temp_frost_protect = HEAT_TEMP_FROST_PROTECT; + } /********************************************************************************************/ diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index aa02d676e..275a5437f 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -554,8 +554,9 @@ void GetFeatures(void) #ifdef USE_PING feature6 |= 0x00000080; // xdrv_38_ping.ino #endif - -// feature6 |= 0x00000100; +#ifdef USE_HEATING + feature6 |= 0x00000100; // xdrv_39_heating.ino +#endif // feature6 |= 0x00000200; // feature6 |= 0x00000400; // feature6 |= 0x00000800; diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 000639505..88511474e 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -324,7 +324,7 @@ enum DevGroupShareItem { DGR_SHARE_POWER = 1, DGR_SHARE_LIGHT_BRI = 2, DGR_SHARE enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER, SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER, - SRC_MAX }; + SRC_HEATING, SRC_MAX }; const char kCommandSource[] PROGMEM = "I|MQTT|Restart|Button|Switch|Backlog|Serial|WebGui|WebCommand|WebConsole|PulseTimer|" "Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote|Shutter"; From ee47415579a04f2664e42e17bbbfe3e782fa869d Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Fri, 17 Apr 2020 10:35:26 +0200 Subject: [PATCH 02/16] Was not propertly committed --- tasmota/xdrv_39_heating.ino | 1125 +++++++++++++++++++++++++++++++++++ 1 file changed, 1125 insertions(+) create mode 100644 tasmota/xdrv_39_heating.ino diff --git a/tasmota/xdrv_39_heating.ino b/tasmota/xdrv_39_heating.ino new file mode 100644 index 000000000..05d8b96b2 --- /dev/null +++ b/tasmota/xdrv_39_heating.ino @@ -0,0 +1,1125 @@ +/* + xdrv_90_heating.ino - Heating controller for Tasmota + 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 . +*/ + +#ifdef USE_HEATING + +#define XDRV_39 39 + +enum HeatingModes { HEAT_OFF, HEAT_AUTOMATIC_OP, HEAT_MANUAL_OP, HEAT_TIME_PLAN }; +enum ControllerModes { CTR_HYBRID, CTR_PI, CTR_RAMP_UP }; +enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP, CTR_HYBRID_PI }; +enum InterfaceStates { IFACE_OFF, IFACE_ON }; +enum HeatingSupportedInputSwitches { + HEATING_INPUT_NONE, + HEATING_INPUT_SWT1 = 1, // Buttons + HEATING_INPUT_SWT2, + HEATING_INPUT_SWT3, + HEATING_INPUT_SWT4 +}; +enum HeatingSupportedOutputRelays { + HEATING_OUTPUT_NONE, + HEATING_OUTPUT_REL1 = 1, // Relays + HEATING_OUTPUT_REL2, + HEATING_OUTPUT_REL3, + HEATING_OUTPUT_REL4, + HEATING_OUTPUT_REL5, + HEATING_OUTPUT_REL6, + HEATING_OUTPUT_REL7, + HEATING_OUTPUT_REL8 +}; + +const char kHeatingCommands[] PROGMEM = "|" D_CMND_HEATINGMODESET "|" D_CMND_TEMPFROSTPROTECTSET "|" + D_CMND_CONTROLLERMODESET "|" D_CMND_INPUTSWITCHSET "|" D_CMND_OUTPUTRELAYSET "|" D_CMND_TIMEALLOWRAMPUPSET "|" + D_CMND_TEMPMEASUREDSET "|" D_CMND_TEMPTARGETSET "|" D_CMND_TIMEPLANSET "|" D_CMND_TEMPTARGETREAD "|" + D_CMND_TEMPMEASUREDREAD "|" D_CMND_TEMPMEASUREDGRDREAD "|" D_CMND_TEMPSENSNUMBERSET "|" + D_CMND_STATEEMERGENCYSET "|" D_CMND_POWERMAXSET "|" 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; + +void (* const HeatingCommand[])(void) PROGMEM = { + &CmndHeatingModeSet, &CmndTempFrostProtectSet, &CmndControllerModeSet, &CmndInputSwitchSet, &CmndOutputRelaySet, + &CmndTimeAllowRampupSet, &CmndTempMeasuredSet, &CmndTempTargetSet, &CmndTimePlanSet, &CmndTempTargetRead, + &CmndTempMeasuredRead, &CmndTempMeasuredGrdRead, &CmndTempSensNumberSet, &CmndStateEmergencySet, + &CmndPowerMaxSet, &CmndTimeManualToAutoSet, &CmndTimeOnLimitSet, &CmndPropBandSet, &CmndTimeResetSet, + &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, &CmndTempHystSet, &CmndTimeMaxActionSet, + &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet, &CmndTempRupDeltOutSet, + &CmndTimeRampupMaxSet, &CmndTimeRampupCycleSet, &CmndTempRampupPiAccErrSet, &CmndTimePiProportRead, + &CmndTimePiIntegrRead }; + +const char DOMOTICZ_MES[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\"}"; + +struct HEATING { + uint32_t counter_seconds = 0; // Counter incremented every second + uint8_t heating_mode = HEAT_OFF; // Operation mode of the heating system + uint8_t controller_mode = CTR_HYBRID; // Operation mode of the heating controller + bool sensor_alive = false; // Bool stating if temperature sensor is alive + bool command_output = false; // Bool stating state to save the command to the output (true = active, false = inactive) + uint8_t phase_hybrid_ctr = CTR_HYBRID_PI; // Phase of the hybrid controller (Ramp-up or PI) + uint8_t status_output = IFACE_OFF; // Status of the output switch + uint16_t temp_target_level = 180; // Target level of the heating in tenths of degrees + uint16_t temp_target_level_ctr = 180; // Target level set for the controller + int16_t temp_measured = 0; // Temperature measurement received from sensor in tenths of degrees + uint32_t timestamp_temp_target_update = 0; // Timestamp of latest target value update + uint32_t timestamp_temp_measured_update = 0; // Timestamp of latest measurement value update + uint32_t timestamp_temp_meas_change_update = 0;// Timestamp of latest measurement value change (> or < to previous) + uint32_t timestamp_output_on = 0; // Timestamp of latest heating output On state + uint32_t timestamp_output_off = 0; // Timestamp of latest heating output Off state + uint32_t timestamp_input_on = 0; // Timestamp of latest input On state + uint32_t time_heating_total = 0; // Time heating on within a specific timeframe + uint32_t time_pi_checkpoint = 0; // Time to finalize the pi control cycle + uint32_t time_pi_changepoint = 0; // Time until switching off output within a pi control cycle + uint32_t time_rampup_checkpoint = 0; // Time to switch from ramp-up controller mode to PI + uint32_t time_rampup_output_off = 0; // Time to switch off relay output within the ramp-up controller + 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 heating system (step response time) + uint32_t time_rampup_nextcycle = 0; // Time where the ramp-up controller shall start the next cycle + uint32_t counter_rampup_cycles = 0; // Counter of ramp-up cycles + int32_t temp_measured_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees per hour + int32_t temp_rampup_meas_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees per hour calculated during ramp-up + int16_t temp_rampup_output_off = 0; // Temperature to swith off relay output within the ramp-up controller in tenths of degrees + 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 + int16_t temp_pi_accum_error = 0; // Temperature accumulated error for the PI controller in tenths of degrees + int16_t temp_pi_error = 0; // Temperature error for the PI controller in tenths of degrees + 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 + uint16_t heating_plan[7][6] = { // Heating plan for the week (3 times/temperatures per day in tenths of degrees) + {0,0,0,0,0,0}, // Monday, format {time/temp, time/temp, time/temp} + {0,0,0,0,0,0}, // Tuesday, format {time/temp, time/temp, time/temp} + {0,0,0,0,0,0}, // Wednesday, format {time/temp, time/temp, time/temp} + {0,0,0,0,0,0}, // Thursday, format {time/temp, time/temp, time/temp} + {0,0,0,0,0,0}, // Friday, format {time/temp, time/temp, time/temp} + {0,0,0,0,0,0}, // Saturday, format {time/temp, time/temp, time/temp} + {0,0,0,0,0,0} // Sunday, format {time/temp, time/temp, time/temp} + }; + bool status_cycle_active = false; // Status showing if cycle is active (Output ON) or not (Output OFF) +} Heating; + +/*********************************************************************************************/ + +void HeatingInit() +{ + ExecuteCommandPower(Settings.output_relay_number, POWER_OFF, SRC_HEATING); // Make sure the Output is OFF +} + +bool HeatingMinuteCounter() +{ + bool result = false; + Heating.counter_seconds++; // increment time + + if ((Heating.counter_seconds % 60) == 0) { + result = true; + } + return(result); +} + +inline bool HeatingSwitchIdValid(uint8_t switchId) +{ + return (switchId >= HEATING_INPUT_SWT1 && switchId <= HEATING_INPUT_SWT4); +} + +inline bool HeatingRelayIdValid(uint8_t relayId) +{ + return (relayId >= HEATING_OUTPUT_REL1 && relayId <= HEATING_OUTPUT_REL8); +} + +uint8_t HeatingSwitchStatus(uint8_t input_switch) +{ + bool ifId = HeatingSwitchIdValid(input_switch); + if(ifId) { + return(SwitchGetVirtual(ifId - HEATING_INPUT_SWT1)); + } + else return 255; +} + +void HeatingSignalProcessingSlow() +{ + if ((uptime - Heating.timestamp_temp_measured_update) > Settings.time_sens_lost) { // Check if sensor alive + Heating.sensor_alive = false; + Heating.temp_measured_gradient = 0; + Heating.temp_measured = 0; + } +} + +void HeatingSignalProcessingFast() +{ + if (HeatingSwitchStatus(Settings.input_switch_number)) { // Check if input switch active and register last update + Heating.timestamp_input_on = uptime; + } +} + +void HeatingCtrState() +{ + switch (Heating.controller_mode) { + case CTR_HYBRID: // Ramp-up phase with gradient control + HeatingHybridCtrPhase(); + break; + case CTR_PI: + break; + case CTR_RAMP_UP: + break; + } +} + +void HeatingHybridCtrPhase() +{ + if (Heating.controller_mode == CTR_HYBRID) { + switch (Heating.phase_hybrid_ctr) { + case CTR_HYBRID_RAMP_UP: // Ramp-up phase with gradient control + // If ramp-up offtime counter has been initalized + // AND ramp-up offtime counter value reached + if((Heating.time_rampup_checkpoint != 0) + && (uptime >= Heating.time_rampup_checkpoint)) { + // Reset pause period + Heating.time_rampup_checkpoint = 0; + // Set PI controller + Heating.phase_hybrid_ctr = CTR_HYBRID_PI; + } + break; + case CTR_HYBRID_PI: // PI controller phase + // 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 + if (((uptime - Heating.timestamp_output_off) > Settings.time_allow_rampup) + && (Heating.temp_target_level != Heating.temp_target_level_ctr) + &&((Heating.temp_target_level - Heating.temp_measured) > Settings.temp_rampup_delta_in)) { + Heating.phase_hybrid_ctr = CTR_HYBRID_RAMP_UP; + Heating.timestamp_rampup_start = uptime; + Heating.temp_rampup_start = Heating.temp_measured; + Heating.temp_rampup_meas_gradient = 0; + Heating.time_rampup_checkpoint = 0; + Heating.time_rampup_deadtime = 0; + Heating.counter_rampup_cycles = 1; + } + break; + } + } +} + +bool HeatStateAutoOrPlanToManual() +{ + bool change_state = false; + + // If switch input is active + // OR temperature sensor is not alive + // then go to manual + if ((HeatingSwitchStatus(Settings.input_switch_number) == 1) + || (Heating.sensor_alive == false)) { + change_state = true; + } + return change_state; +} + +bool HeatStateManualToAuto() +{ + bool change_state; + + // If switch input inactive + // AND no switch input action (time in current state) bigger than a pre-defined time + // then go to automatic + if ((HeatingSwitchStatus(Settings.input_switch_number) == 0) + && ((uptime - Heating.timestamp_input_on) > Settings.time_manual_to_auto)) { + change_state = true; + } + return change_state; +} + +bool HeatStateAllToOff() +{ + bool change_state; + + // If emergency mode then switch OFF the output inmediately + if (Settings.state_emergency) { + Heating.heating_mode = HEAT_OFF; // Emergency switch to HEAT_OFF + } + return change_state; +} + +void HeatingState() +{ + switch (Heating.heating_mode) { + case HEAT_OFF: // State if Off or Emergency + // No change of state possible without external command + break; + case HEAT_AUTOMATIC_OP: // State automatic heating active following to command target temp. + if (HeatStateAllToOff()) { + Heating.heating_mode = HEAT_OFF; // Emergency switch to HEAT_OFF + } + if (HeatStateAutoOrPlanToManual()) { + Heating.heating_mode = HEAT_MANUAL_OP; // If sensor not alive change to HEAT_MANUAL_OP + } + HeatingCtrState(); + break; + case HEAT_MANUAL_OP: // State manual operation following input switch + if (HeatStateAllToOff()) { + Heating.heating_mode = HEAT_OFF; // Emergency switch to HEAT_OFF + } + if (HeatStateManualToAuto()) { + Heating.heating_mode = HEAT_AUTOMATIC_OP; // Input switch inactive and timeout reached change to HEAT_AUTOMATIC_OP + } + break; + case HEAT_TIME_PLAN: // State automatic heating active following set heating plan + if (HeatStateAllToOff()) { + Heating.heating_mode = HEAT_OFF; // Emergency switch to HEAT_OFF + } + if (HeatStateAutoOrPlanToManual()) { + Heating.heating_mode = HEAT_MANUAL_OP; // If sensor not alive change to HEAT_MANUAL_OP + } + HeatingCtrState(); + break; + } +} + +void HeatingOutputRelay(bool active) +{ + // TODO: See if the real output state can be read by f.i. bitRead(power, Settings.output_relay_number)) + // If command received to enable output + // AND current output status is OFF + // then switch output to ON + if ((active == true) + && (Heating.status_output == IFACE_OFF)) { + ExecuteCommandPower(Settings.output_relay_number, POWER_ON, SRC_HEATING); + Heating.timestamp_output_on = uptime; + Heating.status_output = IFACE_ON; + } + // If command received to disable output + // AND current output status is ON + // then switch output to OFF + else if ((active == false) && (Heating.status_output == IFACE_ON)) { + ExecuteCommandPower(Settings.output_relay_number, POWER_OFF, SRC_HEATING); + Heating.timestamp_output_off = uptime; + Heating.status_output = IFACE_OFF; + } +} + +void HeatingCalculatePI() +{ + // Calculate error + Heating.temp_pi_error = Heating.temp_target_level_ctr - Heating.temp_measured; + // Kp = 100/PI.propBand. PI.propBand(Xp) = Proportional range (4K in 4K/200 controller) + Heating.kP_pi = 100 / (uint16_t)(Settings.val_prop_band); + // Calculate proportional + Heating.time_proportional_pi = ((int32_t)(Heating.temp_pi_error * (int16_t)Heating.kP_pi) * Settings.time_pi_cycle) / 1000; + + // Minimum proportional action limiter + // If proportional action is less than the minimum action time + // AND proportional > 0 + // then adjust to minimum value + if ((Heating.time_proportional_pi < abs(Settings.time_min_action)) + && (Heating.time_proportional_pi > 0)) { + Heating.time_proportional_pi = Settings.time_min_action; + } + + if (Heating.time_proportional_pi < 0) { + Heating.time_proportional_pi = 0; + } + else if (Heating.time_proportional_pi > Settings.time_pi_cycle) { + Heating.time_proportional_pi = Settings.time_pi_cycle; + } + + // Calculate integral + Heating.kI_pi = (uint16_t)(((float)Heating.kP_pi * ((float)Settings.time_pi_cycle / (float)Settings.time_reset)) * 100); + + // Reset of antiwindup + // If error does not lay within the integrator scope range, do not use the integral + // and accumulate error = 0 + if (abs(Heating.temp_pi_error) > Settings.temp_reset_anti_windup) { + Heating.time_integral_pi = 0; + Heating.temp_pi_accum_error = 0; + } + // 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 + + // If we are under setpoint + // AND we are within the hysteresis + // AND we are rising + if ((Heating.temp_pi_error >= 0) + && (abs(Heating.temp_pi_error) <= (int16_t)Settings.temp_hysteresis) + && (Heating.temp_measured_gradient > 0)) { + Heating.temp_pi_accum_error += Heating.temp_pi_error; + // Reduce accumulator error 20% in each cycle + Heating.temp_pi_accum_error *= 0.8; + } + // If we are over setpoint + // AND temperature is rising + else if ((Heating.temp_pi_error < 0) + && (Heating.temp_measured_gradient > 0)) { + Heating.temp_pi_accum_error += Heating.temp_pi_error; + // Reduce accumulator error 20% in each cycle + Heating.temp_pi_accum_error *= 0.8; + } + else { + Heating.temp_pi_accum_error += Heating.temp_pi_error; + } + + // Limit lower limit of acumErr to 0 + if (Heating.temp_pi_accum_error < 0) { + Heating.temp_pi_accum_error = 0; + } + + // Integral calculation + Heating.time_integral_pi = ((((int32_t)Heating.temp_pi_accum_error * (int32_t)Heating.kI_pi) / 100) * (int32_t)(Settings.time_pi_cycle)) / 1000; + + // Antiwindup of the integrator + // If integral calculation is bigger than cycle time, adjust result + // to the cycle time and error will not be cummulated]] + if (Heating.time_integral_pi > Settings.time_pi_cycle) { + Heating.time_integral_pi = Settings.time_pi_cycle; + } + } + + // Calculate output + Heating.time_total_pi = Heating.time_proportional_pi + Heating.time_integral_pi; + + // Antiwindup of the output + // If result is bigger than cycle time, the result will be adjusted + // to the cylce time minus safety time and error will not be cummulated]] + if (Heating.time_total_pi > Settings.time_pi_cycle) { + // Limit to cycle time //at least switch down a minimum time + Heating.time_total_pi = Settings.time_pi_cycle; + } + else if (Heating.time_total_pi < 0) { + Heating.time_total_pi = 0; + } + + // Target value limiter + // If target value has been reached or we are over it]] + if (Heating.temp_pi_error <= 0) { + // If we are over the hysteresis or the gradient is positive + if ((abs(Heating.temp_pi_error) > Settings.temp_hysteresis) + || (Heating.temp_measured_gradient >= 0)) { + Heating.time_total_pi = 0; + } + } + // If target value has not been reached + // AND we are withing the histeresis + // AND gradient is positive + // then set value to 0 + else if ((Heating.temp_pi_error > 0) + && (abs(Heating.temp_pi_error) <= Settings.temp_hysteresis) + && (Heating.temp_measured_gradient > 0)) { + Heating.time_total_pi = 0; + } + + // Minimum action limiter + // If result is less than the minimum action time, adjust to minimum value]] + if ((Heating.time_total_pi <= abs(Settings.time_min_action)) + && (Heating.time_total_pi != 0)) { + Heating.time_total_pi = Settings.time_min_action; + } + // Maximum action limiter + // If result is more than the maximum action time, adjust to maximum value]] + else if (Heating.time_total_pi > abs(Settings.time_max_action)) { + Heating.time_total_pi = Settings.time_max_action; + } + // If switched off less time than safety time, do not switch off + else if (Heating.time_total_pi > (Settings.time_pi_cycle - Settings.time_min_turnoff_action)) { + Heating.time_total_pi = Settings.time_pi_cycle; + } + + // Adjust output switch point + Heating.time_pi_changepoint = uptime + Heating.time_total_pi; + // Adjust next cycle point + Heating.time_pi_checkpoint = uptime + Settings.time_pi_cycle; +} + +void HeatingWorkAutomaticPI() +{ + char result_chr[FLOATSZ]; // Remove! + + if ((uptime >= Heating.time_pi_checkpoint) + || (Heating.temp_target_level != Heating.temp_target_level_ctr) + || ((Heating.temp_measured < Heating.temp_target_level) + && (Heating.temp_measured_gradient < 0) + && (Heating.status_cycle_active == false))) { + Heating.temp_target_level_ctr = Heating.temp_target_level; + HeatingCalculatePI(); + // Reset cycle active + Heating.status_cycle_active = false; + } + if (uptime < Heating.time_pi_changepoint) { + Heating.status_cycle_active = true; + Heating.command_output = true; + } + else { + Heating.command_output = false; + } +} + +void HeatingWorkAutomaticRampUp() +{ + uint32_t time_in_rampup; + int16_t temp_delta_rampup; + + // Update timestamp for temperature at start of ramp-up if temperature still dropping + if (Heating.temp_measured < Heating.temp_rampup_start) { + Heating.temp_rampup_start = Heating.temp_measured; + } + + // Update time in ramp-up as well as delta temp + time_in_rampup = uptime - Heating.timestamp_rampup_start; + temp_delta_rampup = Heating.temp_measured - Heating.temp_rampup_start; + // Init command output status to true + Heating.command_output = true; + // Update temperature target level for controller + Heating.temp_target_level_ctr = Heating.temp_target_level; + + // If time in ramp-up < max time + // AND temperature measured < target + if ((time_in_rampup <= Settings.time_rampup_max) + && (Heating.temp_measured < Heating.temp_target_level)) { + // DEADTIME point reached + // If temperature measured minus temperature at start of ramp-up >= threshold + // AND deadtime still 0 + if ((temp_delta_rampup >= Settings.temp_rampup_delta_out) + && (Heating.time_rampup_deadtime == 0)) { + // 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; + time_aux = ((time_in_rampup / 2) - Settings.time_output_delay); + if (time_aux >= Settings.time_output_delay) { + Heating.time_rampup_deadtime = (uint32_t)time_aux; + } + else { + Heating.time_rampup_deadtime = Settings.time_output_delay; + } + // Calculate gradient since start of ramp-up (considering deadtime) in thousandths of º/hour + Heating.temp_rampup_meas_gradient = (int32_t)((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_in_rampup); + Heating.time_rampup_nextcycle = uptime + Settings.time_rampup_cycle; + // Set auxiliary variables + Heating.temp_rampup_cycle = Heating.temp_measured; + Heating.time_rampup_output_off = uptime + Settings.time_rampup_max; + Heating.temp_rampup_output_off = Heating.temp_target_level_ctr; + } + // Gradient calculation every time_rampup_cycle + else if ((Heating.time_rampup_deadtime > 0) && (uptime >= Heating.time_rampup_nextcycle)) { + // 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)) + temp_delta_rampup = Heating.temp_measured - Heating.temp_rampup_cycle; + uint32_t time_total_rampup = Settings.time_rampup_cycle * Heating.counter_rampup_cycles; + // Translate into gradient per hour (thousandths of ° per hour) + Heating.temp_rampup_meas_gradient = int32_t((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_total_rampup); + if (Heating.temp_rampup_meas_gradient > 0) { + // 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) + // x = ((y-y1)/(y2-y1))*(x2-x1) + x1 - deadtime + // Heating.time_rampup_output_off = (uint32_t)(((float)(Heating.temp_target_level_ctr - Heating.temp_rampup_cycle) / (float)temp_delta_rampup) * (float)(time_total_rampup)) + (uint32_t)(Heating.time_rampup_nextcycle - (time_total_rampup)) - Heating.time_rampup_deadtime; + Heating.time_rampup_output_off = (uint32_t)(((float)(Heating.temp_target_level_ctr - Heating.temp_rampup_cycle) * (float)(time_total_rampup)) / (float)temp_delta_rampup) + (uint32_t)(Heating.time_rampup_nextcycle - (time_total_rampup)) - Heating.time_rampup_deadtime; + + // Calculate temperature for switching off the output + // y = (((y2-y1)/(x2-x1))*(x-x1)) + y1 + // Heating.temp_rampup_output_off = (int16_t)(((float)(temp_delta_rampup) / (float)(time_total_rampup * Heating.counter_rampup_cycles)) * (float)(Heating.time_rampup_output_off - (uptime - (time_total_rampup)))) + Heating.temp_rampup_cycle; + Heating.temp_rampup_output_off = (int16_t)(((float)temp_delta_rampup * (float)(Heating.time_rampup_output_off - (uptime - (time_total_rampup)))) / (float)(time_total_rampup * Heating.counter_rampup_cycles)) + Heating.temp_rampup_cycle; + // Set auxiliary variables + Heating.time_rampup_nextcycle = uptime + Settings.time_rampup_cycle; + Heating.temp_rampup_cycle = Heating.temp_measured; + // Reset period counter + Heating.counter_rampup_cycles = 1; + } + else { + // Increase the period counter + Heating.counter_rampup_cycles++; + // Set another period + Heating.time_rampup_nextcycle = uptime + Settings.time_rampup_cycle; + // Reset time_rampup_output_off and temp_rampup_output_off + Heating.time_rampup_output_off = uptime + Settings.time_rampup_max - time_in_rampup; + Heating.temp_rampup_output_off = Heating.temp_target_level_ctr; + } + // Set time to get out of calibration + Heating.time_rampup_checkpoint = Heating.time_rampup_output_off + Heating.time_rampup_deadtime; + } + + // 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 + if ((Heating.time_rampup_deadtime == 0) + || (Heating.time_rampup_checkpoint == 0) + || (uptime < Heating.time_rampup_output_off) + || (Heating.temp_measured < Heating.temp_rampup_output_off) + || (Heating.temp_rampup_meas_gradient <= 0)) { + Heating.command_output = true; + } + else { + Heating.command_output = false; + } + } + else { + // If we have not reached the temperature, start with an initial value for accumulated error for the PI controller + if (Heating.temp_measured < Heating.temp_target_level_ctr) { + Heating.temp_pi_accum_error = Settings.temp_rampup_pi_acc_error; + } + // Set to now time to get out of calibration + Heating.time_rampup_checkpoint = uptime; + // Switch Off output + Heating.command_output = false; + } +} + +void HeatingCtrWork() +{ + switch (Heating.controller_mode) { + case CTR_HYBRID: // Ramp-up phase with gradient control + switch (Heating.phase_hybrid_ctr) { + case CTR_HYBRID_RAMP_UP: + HeatingWorkAutomaticRampUp(); + break; + case CTR_HYBRID_PI: + HeatingWorkAutomaticPI(); + break; + } + break; + case CTR_PI: + HeatingWorkAutomaticPI(); + break; + case CTR_RAMP_UP: + HeatingWorkAutomaticRampUp(); + break; + } +} + +void HeatingPlanTempTarget() +{ + int16_t tmp_minute_delta[3]; // Array of deltas in minute for 3 different time minutes of a day to the current utc time minute of the day + uint8_t time_selected = 0; // Index of time selected + int16_t time_lowest_delta = 1440; // lowest time delta in minutes from the array values to UTC, initiated to 1 day (1440 minutes) + uint8_t day_of_week = RtcTime.day_of_week; // Current day of week (1 = Sun) + + // For each of the three times within the current day of week + for (uint8_t i=0; i<3; i++) { + + // Store time difference between current minute of the day and the minute of the day of each planned time wihtin array + tmp_minute_delta[i] = ((((int16_t)RtcTime.hour * 60) + (int16_t)RtcTime.minute) - (int16_t)Heating.heating_plan[day_of_week - 1][(i * 2)]); + + if ((tmp_minute_delta[i] >= 0) && + (tmp_minute_delta[i] < time_lowest_delta)) { + time_lowest_delta = tmp_minute_delta[i]; + time_selected = i; + } + } + + // Update target value if time delta to selected time is 0 or positive + if ((tmp_minute_delta[time_selected] >= 0) + && (Heating.heating_plan[day_of_week - 1][(2 * time_selected) + 1] >= Settings.temp_frost_protect)) { + Heating.temp_target_level = Heating.heating_plan[day_of_week - 1][(2 * time_selected) + 1]; + } +} + +void HeatingWork() +{ + switch (Heating.heating_mode) { + case HEAT_OFF: // State if Off or Emergency + Heating.command_output = false; + break; + case HEAT_AUTOMATIC_OP: // State automatic heating active following to command target temp. + HeatingCtrWork(); + break; + case HEAT_MANUAL_OP: // State manual operation following input switch + Heating.time_rampup_checkpoint = 0; + break; + case HEAT_TIME_PLAN: // State automatic heating active following set heating plan + // Set target temperature based on plan + HeatingPlanTempTarget(); + HeatingCtrWork(); + break; + } + HeatingOutputRelay(Heating.command_output); +} + +void HeatingDiagnostics() +{ + // TODOs: + // 1. Check time max for output switch on not exceeded + // 2. Check state of output corresponds to command + // 3. Check maximum power at output switch not exceeded +} + +void HeatingController() +{ + HeatingState(); + HeatingWork(); +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +void CmndHeatingModeSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data)); + if ((value >= HEAT_OFF) && (value <= HEAT_TIME_PLAN)) { + Heating.heating_mode = value; + Heating.timestamp_input_on = 0; // Reset last manual switch timer if command set externally + } + } + ResponseCmndNumber((int)Heating.heating_mode); +} + +void CmndTempFrostProtectSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= 0) && (value <= 255)) { + Settings.temp_frost_protect = value; + } + } + ResponseCmndFloat((float)(Settings.temp_frost_protect) / 10, 1); +} + +void CmndControllerModeSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= CTR_RAMP_UP)) { + Heating.controller_mode = value; + } + } + ResponseCmndNumber((int)Heating.controller_mode); +} + +void CmndInputSwitchSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(XdrvMailbox.payload); + if (HeatingSwitchIdValid(value)) { + Settings.input_switch_number = value; + Heating.timestamp_input_on = uptime; + } + } + ResponseCmndNumber((int)Settings.input_switch_number); +} + +void CmndOutputRelaySet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(XdrvMailbox.payload); + if (HeatingRelayIdValid(value)) { + Settings.output_relay_number = value; + } + } + ResponseCmndNumber((int)Settings.output_relay_number); +} + +void CmndTimeAllowRampupSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value < 86400)) { + Settings.time_allow_rampup = value; + } + } + ResponseCmndNumber((int)Settings.time_allow_rampup); +} + +void CmndTempMeasuredSet(void) +{ + if (XdrvMailbox.data_len > 0) { + int16_t value = (int16_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= -1000) && (value <= 1000)) { + uint32_t timestamp = uptime; + // Calculate temperature gradient if temperature value has changed + if (value != Heating.temp_measured) { + int16_t temp_delta = (value - Heating.temp_measured); // in tenths of degrees + uint32_t time_delta = (timestamp - Heating.timestamp_temp_meas_change_update); // in seconds + Heating.temp_measured_gradient = (int32_t)((360000 * (int32_t)temp_delta) / (int32_t)time_delta); // hundreths of degrees per hour + Heating.temp_measured = value; + Heating.timestamp_temp_meas_change_update = timestamp; + } + Heating.timestamp_temp_measured_update = timestamp; + Heating.sensor_alive = true; + } + } + ResponseCmndFloat(((float)Heating.temp_measured) / 10, 1); +} + +void CmndTempTargetSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint16_t value = (uint16_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= -1000) + && (value <= 1000) + && (value >= Settings.temp_frost_protect)) { + Heating.temp_target_level = value; + Heating.timestamp_temp_target_update = uptime; + } + } + ResponseCmndFloat(((float)Heating.temp_target_level) / 10, 1); +} + +void CmndTimePlanSet(void) +{ + // TimePlanSet1 05:00/21.0, 15:30/22.5, 23:00/18.0 - TimePlanSet1 3 sets of hour and related target temperature for Monday´s + // TimePlanSet2 05:00/21.0, 15:30/22.5, 23:00/18.0 - TimePlanSet2 3 sets of hour and related target temperature for Tuesday´s + // TimePlanSet3 05:00/21.0, 15:30/22.5, 23:00/18.0 - TimePlanSet3 3 sets of hour and related target temperature for Wednesday´s + // TimePlanSet4 05:00/21.0, 15:30/22.5, 23:00/18.0 - TimePlanSet4 3 sets of hour and related target temperature for Thursday´s + // TimePlanSet5 05:00/21.0, 15:30/22.5, 23:00/18.0 - TimePlanSet5 3 sets of hour and related target temperature for Friday´s + // TimePlanSet6 05:00/21.0, 15:30/22.5, 23:00/18.0 - TimePlanSet6 3 sets of hour and related target temperature for Saturday´s + // TimePlanSet7 05:00/21.0, 15:30/22.5, 23:00/18.0 - TimePlanSet7 3 sets of hour and related target temperature for Sunday´s + + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 7)) { + if (XdrvMailbox.data_len > 0) { + uint8_t aux_index = 0; + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); // Extract first pair time/temp -> 05:00/21.0 + + while ((str != nullptr) && (aux_index < 6)) { + char *q; + char *minutes; + char *temp; + char *temp_decimal; + float temp_f; + uint8_t str_len =strlen(str); + + // Check basic structure of the data, length matching, position of ":" and "/" matching + if((str_len > 0) && (str_len < 11) && (str[2] == ':') && (str[5] == '/')) { + // Extract the time + uint16_t value = strtol(str, &q, 10); // extract 5 + + char value_c[33]; + dtostrfd((double)value, 0, value_c); + + if ((value >= 0) && (value < 24)) { // Below 24 is hours + uint8_t day_of_week = XdrvMailbox.index -1; + minutes = strtok_r(nullptr, ":/.", &q); + Heating.heating_plan[day_of_week][aux_index] = (value * 60);// Multiply hours by 60 minutes + if (minutes) { + value = strtol(minutes, nullptr, 10); // extract 00 + if (value <= 59) { + Heating.heating_plan[day_of_week][aux_index] += value; + } + } + aux_index++; + + // Extract the whole-number part of the temperature + temp = strtok_r(nullptr, ":/.", &q); + + if (temp) { + value = strtol(temp, nullptr, 10); // extract 00 + temp_f = CharToFloat((char*)temp); + temp_decimal = strtok_r(nullptr, ":/.", &q); + if (temp_decimal) { + value = strtol(temp_decimal, nullptr, 10); // extract decimal part + if (value <= 9) { + temp_f += (CharToFloat((char*)temp_decimal) / 10); + } + } + if ((temp_f > -100) && (temp_f < 100)) { + Heating.heating_plan[day_of_week][aux_index] = (uint16_t)(temp_f * 10); // Multiply degrees by 10 to convert to decidegrees + } + } + aux_index++; + } + else { + aux_index += 2; + } + } + else { + aux_index += 2; + } + str = strtok_r(nullptr, ", ", &p); + } + } + uint8_t index_d = XdrvMailbox.index; + char day[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + char *day_p = &day[index_d - 1][0]; + char index_s[strlen(day_p)+1]; + strcpy(index_s, day_p); + char temp1[5]; + char temp2[5]; + char temp3[5]; + dtostrfd((double)((float)Heating.heating_plan[index_d - 1][1] / 10), 1, temp1); + dtostrfd((double)((float)Heating.heating_plan[index_d - 1][3] / 10), 1, temp2); + dtostrfd((double)((float)Heating.heating_plan[index_d - 1][5] / 10), 1, temp3); + + Response_P(PSTR("{\"%s\":{\"1stTime\":{\"Time\":\"%s\",\"Temperature\":\"%s\"},\"2ndTime\":{\"Time\":\"%s\",\"Temperature\":\"%s\"},\"3rdTime\":{\"Time\":\"%s\",\"Temperature\":\"%s\"}}"), + index_s, + GetMinuteTime(Heating.heating_plan[index_d - 1][0]).c_str(),temp1, + GetMinuteTime(Heating.heating_plan[index_d - 1][2]).c_str(),temp2, + GetMinuteTime(Heating.heating_plan[index_d - 1][4]).c_str(),temp3); + } +} + +void CmndTempTargetRead(void) +{ + ResponseCmndFloat(((float)Heating.temp_target_level) / 10, 1); +} + +void CmndTempMeasuredRead(void) +{ + ResponseCmndFloat((float)(Heating.temp_measured) / 10, 1); +} + +void CmndTempMeasuredGrdRead(void) +{ + ResponseCmndFloat((float)(Heating.temp_measured_gradient) / 1000, 1); +} + +void CmndTempSensNumberSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 255)) { + Settings.temp_sens_number = value; + } + } + ResponseCmndNumber((int)Settings.temp_sens_number); +} + +void CmndStateEmergencySet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 1)) { + Settings.state_emergency = (bool)value; + } + } + ResponseCmndNumber((int)Settings.state_emergency); +} + +void CmndPowerMaxSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint16_t value = (uint16_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 1300)) { + Settings.power_max = value; + } + } + ResponseCmndNumber((int)Settings.power_max); +} + +void CmndTimeManualToAutoSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_manual_to_auto = value; + } + } + ResponseCmndNumber((int)Settings.time_manual_to_auto); +} + +void CmndTimeOnLimitSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_on_limit = value; + } + } + ResponseCmndNumber((int)Settings.time_on_limit); +} + +void CmndPropBandSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 20)) { + Settings.val_prop_band = value; + } + } + ResponseCmndNumber((int)Settings.val_prop_band); +} + +void CmndTimeResetSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_reset = value; + } + } + ResponseCmndNumber((int)Settings.time_reset); +} + +void CmndTimePiCycleSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_pi_cycle = value; + } + } + ResponseCmndNumber((int)Settings.time_pi_cycle); +} + +void CmndTempAntiWindupResetSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= (float)(0)) && (value <= (float)(100.0))) { + Settings.temp_reset_anti_windup = value; + } + } + ResponseCmndFloat((float)(Settings.temp_reset_anti_windup) / 10, 1); +} + +void CmndTempHystSet(void) +{ + if (XdrvMailbox.data_len > 0) { + int8_t value = (int8_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= -100) && (value <= 100)) { + Settings.temp_hysteresis = value; + } + } + ResponseCmndFloat((float)(Settings.temp_hysteresis) / 10, 1); +} + +void CmndTimeMaxActionSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_max_action = value; + } + } + ResponseCmndNumber((int)Settings.time_max_action); +} + +void CmndTimeMinActionSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_min_action = value; + } + } + ResponseCmndNumber((int)Settings.time_min_action); +} + +void CmndTimeMinTurnoffActionSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_min_turnoff_action = value; + } + } + ResponseCmndNumber((int)Settings.time_min_turnoff_action); +} + +void CmndTempRupDeltInSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= 0) && (value <= 100)) { + Settings.temp_rampup_delta_in = value; + } + } + ResponseCmndFloat((float)(Settings.temp_rampup_delta_in) / 10, 1); +} + +void CmndTempRupDeltOutSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= 0) && (value <= 100)) { + Settings.temp_rampup_delta_out = value; + } + } + ResponseCmndFloat((float)(Settings.temp_rampup_delta_out) / 10, 1); +} + +void CmndTimeRampupMaxSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_rampup_max = value; + } + } + ResponseCmndNumber((int)Settings.time_rampup_max); +} + +void CmndTimeRampupCycleSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint32_t value = (uint32_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= 86400)) { + Settings.time_rampup_cycle = value; + } + } + ResponseCmndNumber((int)Settings.time_rampup_cycle); +} + +void CmndTempRampupPiAccErrSet(void) +{ + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10); + if ((value >= 0) && (value <= 250)) { + Settings.temp_rampup_pi_acc_error = value; + } + } + ResponseCmndFloat((float)(Settings.temp_rampup_pi_acc_error) / 10, 1); +} + +void CmndTimePiProportRead(void) +{ + ResponseCmndNumber((int)Heating.time_proportional_pi); +} + +void CmndTimePiIntegrRead(void) +{ + ResponseCmndNumber((int)Heating.time_integral_pi); +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv39(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_INIT: + HeatingInit(); + break; + case FUNC_LOOP: + HeatingSignalProcessingFast(); + HeatingDiagnostics(); + break; + case FUNC_SERIAL: + break; + case FUNC_EVERY_SECOND: + if (HeatingMinuteCounter()) { + HeatingSignalProcessingSlow(); + HeatingController(); + } + break; + case FUNC_COMMAND: + result = DecodeCommand(kHeatingCommands, HeatingCommand); + break; + } + return result; +} + +#endif // USE_HEATING From 1e1a2c1807146f214a508ab5b661de8f09996bbb Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 17 Apr 2020 11:57:09 +0200 Subject: [PATCH 03/16] Add config version tag - Add config version tag - Bump version 8.2.0.4 --- tasmota/settings.h | 4 ++-- tasmota/settings.ino | 9 +++++++++ tasmota/tasmota_version.h | 2 +- tasmota/xdrv_01_webserver.ino | 10 ++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index 6d7962723..02ce55123 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -516,7 +516,6 @@ struct SYSCFG { uint8_t wifi_bssid[6]; // F0A uint8_t as3935_sensor_cfg[5]; // F10 As3935IntCfg as3935_functions; // F15 - //uint8_t free_f35; As3935Param as3935_parameter; // F16 uint64_t zb_ext_panid; // F18 uint64_t zb_precfgkey_l; // F20 @@ -525,8 +524,9 @@ struct SYSCFG { uint8_t zb_channel; // F32 uint8_t zb_free_byte; // F33 uint16_t pms_wake_interval; // F34 + uint8_t config_version; // F36 - uint8_t free_f36[70]; // F36 - Decrement if adding new Setting variables just above and below + uint8_t free_f37[69]; // F37 - Decrement if adding new Setting variables just above and below // Only 32 bit boundary variables below uint8_t time_output_delay; // F7C diff --git a/tasmota/settings.ino b/tasmota/settings.ino index ed2dd021f..b63c533f9 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -1365,6 +1365,15 @@ void SettingsDelta(void) } #endif // ESP8266 + if (Settings.version < 0x08020004) { +#ifdef ESP8266 + Settings.config_version = 0; // ESP8266 (Has been 0 for long time) +#endif // ESP8266 +#ifdef ESP32 + Settings.config_version = 1; // ESP32 +#endif // ESP32 + } + Settings.version = VERSION; SettingsSave(1); } diff --git a/tasmota/tasmota_version.h b/tasmota/tasmota_version.h index 3b8314f65..4014ea4d5 100644 --- a/tasmota/tasmota_version.h +++ b/tasmota/tasmota_version.h @@ -20,7 +20,7 @@ #ifndef _TASMOTA_VERSION_H_ #define _TASMOTA_VERSION_H_ -const uint32_t VERSION = 0x08020003; +const uint32_t VERSION = 0x08020004; // Lowest compatible version const uint32_t VERSION_COMPATIBLE = 0x07010006; diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 8b5806938..5660a231d 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -2509,6 +2509,16 @@ void HandleUploadLoop(void) } else { valid_settings = (settings_buffer[0] == CONFIG_FILE_SIGN); } + + if (valid_settings) { +#ifdef ESP8266 + valid_settings = (0 == settings_buffer[0xF36]); // Settings.config_version +#endif // ESP8266 +#ifdef ESP32 + valid_settings = (1 == settings_buffer[0xF36]); // Settings.config_version +#endif // ESP32 + } + if (valid_settings) { SettingsDefaultSet2(); memcpy((char*)&Settings +16, settings_buffer +16, sizeof(Settings) -16); From a82b87aaea0f3ae2cada17e3578a7c6b63094d1a Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 17 Apr 2020 12:08:43 +0200 Subject: [PATCH 04/16] Fix compile warning --- tasmota/xdrv_20_hue.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino index f9e21c3a3..201491d09 100644 --- a/tasmota/xdrv_20_hue.ino +++ b/tasmota/xdrv_20_hue.ino @@ -542,6 +542,7 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) { } } else { #endif +/* switch(on) { case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE); @@ -549,6 +550,8 @@ void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) { case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE); break; } +*/ + ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE); response += buf; resp = true; #ifdef USE_SHUTTER From 5c19a01cece644b4cd4055ce224ed9c9caf9d2b1 Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Fri, 17 Apr 2020 13:39:02 +0200 Subject: [PATCH 05/16] Merge commit '431ad4256545abd953589c1455a90164dcde5b8a' into Heating --- tasmota/settings.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tasmota/settings.h b/tasmota/settings.h index 02ce55123..6f696db8a 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -516,6 +516,7 @@ struct SYSCFG { uint8_t wifi_bssid[6]; // F0A uint8_t as3935_sensor_cfg[5]; // F10 As3935IntCfg as3935_functions; // F15 + //uint8_t free_f35; As3935Param as3935_parameter; // F16 uint64_t zb_ext_panid; // F18 uint64_t zb_precfgkey_l; // F20 From 79657014b8a70b2fb4ac4ca1e89c1fe53f57943f Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Fri, 17 Apr 2020 13:50:39 +0200 Subject: [PATCH 06/16] no message --- tasmota/settings.h | 1 - 1 file changed, 1 deletion(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index 6f696db8a..02ce55123 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -516,7 +516,6 @@ struct SYSCFG { uint8_t wifi_bssid[6]; // F0A uint8_t as3935_sensor_cfg[5]; // F10 As3935IntCfg as3935_functions; // F15 - //uint8_t free_f35; As3935Param as3935_parameter; // F16 uint64_t zb_ext_panid; // F18 uint64_t zb_precfgkey_l; // F20 From 72ca17815436c7d5d066a74aab54833ddc060da0 Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Fri, 17 Apr 2020 14:02:27 +0200 Subject: [PATCH 07/16] Revert "Update settings.ino" This reverts commit 431ad4256545abd953589c1455a90164dcde5b8a. --- tasmota/settings.ino | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/tasmota/settings.ino b/tasmota/settings.ino index b63c533f9..1769b676d 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -1001,34 +1001,6 @@ void SettingsDefaultSet2(void) Settings.flag3.shutter_mode = SHUTTER_SUPPORT; Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS; Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES; - - // Heating - Settings.energy_heating_output_max = HEATING_ENERGY_OUTPUT_MAX; - Settings.time_output_delay = HEATING_TIME_OUTPUT_DELAY; - Settings.temp_rampup_pi_acc_error = HEATING_TEMP_PI_RAMPUP_ACC_E; - Settings.temp_rampup_delta_out = HEATING_TEMP_RAMPUP_DELTA_OUT; - Settings.temp_rampup_delta_in = HEATING_TEMP_RAMPUP_DELTA_IN; - Settings.output_relay_number = HEATING_RELAY_NUMBER; - Settings.input_switch_number = HEATING_SWITCH_NUMBER; - Settings.time_allow_rampup = HEATING_TIME_ALLOW_RAMPUP; - Settings.time_rampup_max = HEATING_TIME_RAMPUP_MAX; - Settings.time_rampup_cycle = HEATING_TIME_RAMPUP_CYCLE; - Settings.time_sens_lost = HEAT_TIME_SENS_LOST; - Settings.temp_sens_number = HEAT_TEMP_SENS_NUMBER; - Settings.state_emergency = HEAT_STATE_EMERGENCY; - Settings.power_max = HEAT_POWER_MAX; - Settings.time_manual_to_auto = HEAT_TIME_MANUAL_TO_AUTO; - Settings.time_on_limit = HEAT_TIME_ON_LIMIT; - Settings.time_reset = HEAT_TIME_RESET; - Settings.time_pi_cycle = HEAT_TIME_PI_CYCLE; - Settings.time_max_action = HEAT_TIME_MAX_ACTION; - Settings.time_min_action = HEAT_TIME_MIN_ACTION; - Settings.time_min_turnoff_action = HEAT_TIME_MIN_TURNOFF_ACTION; - Settings.val_prop_band = HEAT_PROP_BAND; - Settings.temp_reset_anti_windup = HEAT_TEMP_RESET_ANTI_WINDUP; - Settings.temp_hysteresis = HEAT_TEMP_HYSTERESIS; - Settings.temp_frost_protect = HEAT_TEMP_FROST_PROTECT; - } /********************************************************************************************/ From e347c26eff49656c4777cc9134d0b46687ce6840 Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Fri, 17 Apr 2020 14:02:35 +0200 Subject: [PATCH 08/16] Revert "Update i18n.h" This reverts commit 56788a339f0cd7b013182eaa815b2d32aeabd3bd. --- tasmota/i18n.h | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 2b5487a57..9f99d7c1d 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -577,40 +577,6 @@ #define D_CMND_PING "Ping" #define D_JSON_PING "Ping" -// Commands xdrv_90_heating.ino -#define D_CMND_HEATINGMODESET "HeatingModeSet" -#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" -#define D_CMND_TEMPMEASUREDSET "TempMeasuredSet" -#define D_CMND_TEMPTARGETSET "TempTargetSet" -#define D_CMND_TIMEPLANSET "TimePlanSet" -#define D_CMND_TEMPTARGETREAD "TempTargetRead" -#define D_CMND_TEMPMEASUREDREAD "TempMeasuredRead" -#define D_CMND_TEMPMEASUREDGRDREAD "TempMeasuredGrdRead" -#define D_CMND_TEMPSENSNUMBERSET "TempSensNumberSet" -#define D_CMND_STATEEMERGENCYSET "StateEmergencySet" -#define D_CMND_POWERMAXSET "PowerMaxSet" -#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" - // Commands xsns_02_analog.ino #define D_CMND_ADCPARAM "AdcParam" From bba829883bfe35dea5ab53bc9d17e8e81a6b236f Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Fri, 17 Apr 2020 14:11:21 +0200 Subject: [PATCH 09/16] Update --- tasmota/i18n.h | 34 ++++++++++++++++++++++++++++++++++ tasmota/settings.ino | 28 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 9f99d7c1d..2b5487a57 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -577,6 +577,40 @@ #define D_CMND_PING "Ping" #define D_JSON_PING "Ping" +// Commands xdrv_90_heating.ino +#define D_CMND_HEATINGMODESET "HeatingModeSet" +#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" +#define D_CMND_TEMPMEASUREDSET "TempMeasuredSet" +#define D_CMND_TEMPTARGETSET "TempTargetSet" +#define D_CMND_TIMEPLANSET "TimePlanSet" +#define D_CMND_TEMPTARGETREAD "TempTargetRead" +#define D_CMND_TEMPMEASUREDREAD "TempMeasuredRead" +#define D_CMND_TEMPMEASUREDGRDREAD "TempMeasuredGrdRead" +#define D_CMND_TEMPSENSNUMBERSET "TempSensNumberSet" +#define D_CMND_STATEEMERGENCYSET "StateEmergencySet" +#define D_CMND_POWERMAXSET "PowerMaxSet" +#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" + // Commands xsns_02_analog.ino #define D_CMND_ADCPARAM "AdcParam" diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 1769b676d..b63c533f9 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -1001,6 +1001,34 @@ void SettingsDefaultSet2(void) Settings.flag3.shutter_mode = SHUTTER_SUPPORT; Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS; Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES; + + // Heating + Settings.energy_heating_output_max = HEATING_ENERGY_OUTPUT_MAX; + Settings.time_output_delay = HEATING_TIME_OUTPUT_DELAY; + Settings.temp_rampup_pi_acc_error = HEATING_TEMP_PI_RAMPUP_ACC_E; + Settings.temp_rampup_delta_out = HEATING_TEMP_RAMPUP_DELTA_OUT; + Settings.temp_rampup_delta_in = HEATING_TEMP_RAMPUP_DELTA_IN; + Settings.output_relay_number = HEATING_RELAY_NUMBER; + Settings.input_switch_number = HEATING_SWITCH_NUMBER; + Settings.time_allow_rampup = HEATING_TIME_ALLOW_RAMPUP; + Settings.time_rampup_max = HEATING_TIME_RAMPUP_MAX; + Settings.time_rampup_cycle = HEATING_TIME_RAMPUP_CYCLE; + Settings.time_sens_lost = HEAT_TIME_SENS_LOST; + Settings.temp_sens_number = HEAT_TEMP_SENS_NUMBER; + Settings.state_emergency = HEAT_STATE_EMERGENCY; + Settings.power_max = HEAT_POWER_MAX; + Settings.time_manual_to_auto = HEAT_TIME_MANUAL_TO_AUTO; + Settings.time_on_limit = HEAT_TIME_ON_LIMIT; + Settings.time_reset = HEAT_TIME_RESET; + Settings.time_pi_cycle = HEAT_TIME_PI_CYCLE; + Settings.time_max_action = HEAT_TIME_MAX_ACTION; + Settings.time_min_action = HEAT_TIME_MIN_ACTION; + Settings.time_min_turnoff_action = HEAT_TIME_MIN_TURNOFF_ACTION; + Settings.val_prop_band = HEAT_PROP_BAND; + Settings.temp_reset_anti_windup = HEAT_TEMP_RESET_ANTI_WINDUP; + Settings.temp_hysteresis = HEAT_TEMP_HYSTERESIS; + Settings.temp_frost_protect = HEAT_TEMP_FROST_PROTECT; + } /********************************************************************************************/ From 49652598de19877918ae08ff78e3034b16b77efc Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 17 Apr 2020 14:40:09 +0200 Subject: [PATCH 10/16] Prep ESP32 template --- tasmota/tasmota_template_ESP32.h | 68 +++++--------------------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/tasmota/tasmota_template_ESP32.h b/tasmota/tasmota_template_ESP32.h index b2d7a5846..59820893f 100644 --- a/tasmota/tasmota_template_ESP32.h +++ b/tasmota/tasmota_template_ESP32.h @@ -46,34 +46,28 @@ // Supported hardware modules enum SupportedModules { WEMOS, - ESP32_CAM, MAXMODULE }; -const char kModuleNames[] PROGMEM = - "WeMos D1 ESP32|ESP32 CAM|" - ; +const char kModuleNames[] PROGMEM = "ESP32-DevKit"; // Default module settings -const uint8_t kModuleNiceList[MAXMODULE] PROGMEM = { - WEMOS, - ESP32_CAM -}; +const uint8_t kModuleNiceList[MAXMODULE] PROGMEM = { WEMOS }; const mytmplt kModules[MAXMODULE] PROGMEM = { - { // "WeMos D1 ESP32", // Any ESP32 device like WeMos and NodeMCU hardware (ESP32) + { // WEMOS - Espressif ESP32-DevKitC - Any ESP32 device like WeMos and NodeMCU hardware (ESP32) GPIO_USER, //0 (I)O GPIO0, ADC2_CH1, TOUCH1, RTC_GPIO11, CLK_OUT1, EMAC_TX_CLK GPIO_USER, //1 IO TXD0 GPIO1, U0TXD, CLK_OUT3, EMAC_RXD2 GPIO_USER, //2 IO GPIO2, ADC2_CH2, TOUCH2, RTC_GPIO12, HSPIWP, HS2_DATA0, SD_DATA0 GPIO_USER, //3 IO RXD0 GPIO3, U0RXD, CLK_OUT2 GPIO_USER, //4 IO GPIO4, ADC2_CH0, TOUCH0, RTC_GPIO10, HSPIHD, HS2_DATA1, SD_DATA1, EMAC_TX_ER GPIO_USER, //5 IO GPIO5, VSPICS0, HS1_DATA6, EMAC_RX_CLK -// 0, //6 -// 0, //7 -// 0, //8 -// 0, //9 -// 0, //10 -// 0, //11 + //6 IO GPIO6, Flash CLK + //7 IO GPIO7, Flash D0 + //8 IO GPIO8, Flash D1 + //9 IO GPIO9, Flash D2 + //10 IO GPIO10, Flash D3 + //11 IO GPIO11, Flash CMD GPIO_USER, //12 (I)O GPIO12, ADC2_CH5, TOUCH5, RTC_GPIO15, MTDI, HSPIQ, HS2_DATA2, SD_DATA2, EMAC_TXD3 (If driven High, flash voltage (VDD_SDIO) is 1.8V not default 3.3V. Has internal pull-down, so unconnected = Low = 3.3V. May prevent flashing and/or booting if 3.3V flash is connected and pulled high. See ESP32 datasheet for more details.) GPIO_USER, //13 IO GPIO13, ADC2_CH4, TOUCH4, RTC_GPIO14, MTCK, HSPID, HS2_DATA3, SD_DATA3, EMAC_RX_ER GPIO_USER, //14 IO GPIO14, ADC2_CH6, TOUCH6, RTC_GPIO16, MTMS, HSPICLK, HS2_CLK, SD_CLK, EMAC_TXD2 @@ -83,49 +77,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { GPIO_USER, //18 IO GPIO18, VSPICLK, HS1_DATA7 GPIO_USER, //19 IO GPIO19, VSPIQ, U0CTS, EMAC_TXD0 0, //20 - 0, //21 IO GPIO21, VSPIHD, EMAC_TX_EN - GPIO_USER, //22 IO LED GPIO22, VSPIWP, U0RTS, EMAC_TXD1 - GPIO_USER, //23 IO GPIO23, VSPID, HS1_STROBE - 0, //24 - GPIO_USER, //25 IO GPIO25, DAC_1, ADC2_CH8, RTC_GPIO6, EMAC_RXD0 - GPIO_USER, //26 IO GPIO26, DAC_2, ADC2_CH9, RTC_GPIO7, EMAC_RXD1 - GPIO_USER, //27 IO GPIO27, ADC2_CH7, TOUCH7, RTC_GPIO17, EMAC_RX_DV - 0, //28 - 0, //29 - 0, //30 - 0, //31 - GPIO_USER, //32 IO GPIO32, XTAL_32K_P (32.768 kHz crystal oscillator input), ADC1_CH4, TOUCH9, RTC_GPIO9 - GPIO_USER, //33 IO GPIO33, XTAL_32K_N (32.768 kHz crystal oscillator output), ADC1_CH5, TOUCH8, RTC_GPIO8 - GPIO_USER, //34 I NO PULLUP GPIO34, ADC1_CH6, RTC_GPIO4 - GPIO_USER, //35 I NO PULLUP GPIO35, ADC1_CH7, RTC_GPIO5 - GPIO_USER, //36 I NO PULLUP GPIO36, SENSOR_VP, ADC_H, ADC1_CH0, RTC_GPIO0 - 0, //37 NO PULLUP - 0, //38 NO PULLUP - GPIO_USER //39 I NO PULLUP GPIO39, SENSOR_VN, ADC1_CH3, ADC_H, RTC_GPIO3 - }, - { //"ESP32 CAM", - GPIO_USER, //0 (I)O GPIO0, ADC2_CH1, TOUCH1, RTC_GPIO11, CLK_OUT1, EMAC_TX_CLK - GPIO_USER, //1 IO TXD0 GPIO1, U0TXD, CLK_OUT3, EMAC_RXD2 - GPIO_USER, //2 IO GPIO2, ADC2_CH2, TOUCH2, RTC_GPIO12, HSPIWP, HS2_DATA0, SD_DATA0 - GPIO_USER, //3 IO RXD0 GPIO3, U0RXD, CLK_OUT2 - GPIO_USER, //4 IO GPIO4, ADC2_CH0, TOUCH0, RTC_GPIO10, HSPIHD, HS2_DATA1, SD_DATA1, EMAC_TX_ER - GPIO_USER, //5 IO GPIO5, VSPICS0, HS1_DATA6, EMAC_RX_CLK -// 0, //6 -// 0, //7 -// 0, //8 -// 0, //9 -// 0, //10 -// 0, //11 - GPIO_USER, //12 (I)O GPIO12, ADC2_CH5, TOUCH5, RTC_GPIO15, MTDI, HSPIQ, HS2_DATA2, SD_DATA2, EMAC_TXD3 (If driven High, flash voltage (VDD_SDIO) is 1.8V not default 3.3V. Has internal pull-down, so unconnected = Low = 3.3V. May prevent flashing and/or booting if 3.3V flash is connected and pulled high. See ESP32 datasheet for more details.) - GPIO_USER, //13 IO GPIO13, ADC2_CH4, TOUCH4, RTC_GPIO14, MTCK, HSPID, HS2_DATA3, SD_DATA3, EMAC_RX_ER - GPIO_USER, //14 IO GPIO14, ADC2_CH6, TOUCH6, RTC_GPIO16, MTMS, HSPICLK, HS2_CLK, SD_CLK, EMAC_TXD2 - GPIO_USER, //15 (I)O GPIO15, ADC2_CH3, TOUCH3, MTDO, HSPICS0, RTC_GPIO13, HS2_CMD, SD_CMD, EMAC_RXD3 (If driven Low, silences boot messages from normal boot. Has internal pull-up, so unconnected = High = normal output.) - GPIO_USER, //16 IO GPIO16, HS1_DATA4, U2RXD, EMAC_CLK_OUT - GPIO_USER, //17 IO GPIO17, HS1_DATA5, U2TXD, EMAC_CLK_OUT_180 - GPIO_USER, //18 IO GPIO18, VSPICLK, HS1_DATA7 - GPIO_USER, //19 IO GPIO19, VSPIQ, U0CTS, EMAC_TXD0 - 0, //20 - 0, //21 IO GPIO21, VSPIHD, EMAC_TX_EN + GPIO_USER, //21 IO GPIO21, VSPIHD, EMAC_TX_EN GPIO_USER, //22 IO LED GPIO22, VSPIWP, U0RTS, EMAC_TXD1 GPIO_USER, //23 IO GPIO23, VSPID, HS1_STROBE 0, //24 From ee98151834bd0862751565d43c8a62d9d2abf0da Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 17 Apr 2020 16:17:01 +0200 Subject: [PATCH 11/16] Provide ESP32 base module support --- tasmota/support.ino | 28 +++++++-- tasmota/support_command.ino | 5 +- tasmota/tasmota_template_ESP32.h | 99 ++++++++++++++++---------------- tasmota/xdrv_01_webserver.ino | 16 ++---- 4 files changed, 81 insertions(+), 67 deletions(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index 0435293e6..65605abd3 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -100,7 +100,7 @@ uint32_t ResetReason(void) REASON_EXT_SYS_RST = 6 // "External System" external system reset */ #ifdef ESP8266 - return resetInfo.reason; // Returns Tasmota reason codes + return resetInfo.reason; #else return ESP_ResetInfoReason(); #endif @@ -1131,7 +1131,11 @@ void ModuleGpios(myio *gp) if (USER_MODULE == Settings.module) { memcpy(&src, &Settings.user_template.gp, sizeof(mycfgio)); } else { +#ifdef ESP8266 memcpy_P(&src, &kModules[Settings.module].gp, sizeof(mycfgio)); +#else // ESP32 + memcpy_P(&src, &kModules.gp, sizeof(mycfgio)); +#endif // ESP8266 - ESP32 } // 11 85 00 85 85 00 00 00 15 38 85 00 00 81 @@ -1142,10 +1146,9 @@ void ModuleGpios(myio *gp) #ifdef ESP8266 if (6 == i) { j = 9; } if (8 == i) { j = 12; } -#endif // ESP8266 -#ifdef ESP32 +#else // ESP32 if (6 == i) { j = 12; } -#endif // ESP32 +#endif // ESP8266 - ESP32 dest[j] = src[i]; j++; } @@ -1158,11 +1161,24 @@ gpio_flag ModuleFlag(void) { gpio_flag flag; +#ifdef ESP8266 if (USER_MODULE == Settings.module) { flag = Settings.user_template.flag; } else { memcpy_P(&flag, &kModules[Settings.module].flag, sizeof(gpio_flag)); } +#else // ESP32 + if (USER_MODULE == Settings.module) { +/* + gpio_flag gpio_adc0; + memcpy_P(&gpio_adc0, &Settings.user_template.gp + ADC0_PIN - MIN_FLASH_PINS, sizeof(gpio_flag)); + flag = Settings.user_template.flag.data + gpio_adc0.data; +*/ + memcpy_P(&flag, &Settings.user_template.gp + ADC0_PIN - MIN_FLASH_PINS, sizeof(gpio_flag)); + } else { + memcpy_P(&flag, &kModules.gp + ADC0_PIN - MIN_FLASH_PINS, sizeof(gpio_flag)); + } +#endif // ESP8266 - ESP32 return flag; } @@ -1173,7 +1189,11 @@ void ModuleDefault(uint32_t module) Settings.user_template_base = module; char name[TOPSZ]; SettingsUpdateText(SET_TEMPLATE_NAME, GetTextIndexed(name, sizeof(name), module, kModuleNames)); +#ifdef ESP8266 memcpy_P(&Settings.user_template, &kModules[module], sizeof(mytmplt)); +#else // ESP32 + memcpy_P(&Settings.user_template, &kModules, sizeof(mytmplt)); +#endif // ESP8266 - ESP32 } void SetModuleType(void) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 0d4e12236..28eae87f1 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -1117,10 +1117,9 @@ void CmndTemplate(void) #ifdef ESP8266 if (6 == i) { j = 9; } if (8 == i) { j = 12; } -#endif // ESP8266 -#ifdef ESP32 +#else // ESP32 if (6 == i) { j = 12; } -#endif // ESP32 +#endif // ESP8266 - ESP32 if (my_module.io[j] > GPIO_NONE) { Settings.user_template.gp.io[i] = my_module.io[j]; } diff --git a/tasmota/tasmota_template_ESP32.h b/tasmota/tasmota_template_ESP32.h index 59820893f..967da9400 100644 --- a/tasmota/tasmota_template_ESP32.h +++ b/tasmota/tasmota_template_ESP32.h @@ -45,58 +45,61 @@ /********************************************************************************************/ // Supported hardware modules enum SupportedModules { - WEMOS, - MAXMODULE -}; + WEMOS, ESP32_CAM_AITHINKER, + MAXMODULE}; -const char kModuleNames[] PROGMEM = "ESP32-DevKit"; +const char kModuleNames[] PROGMEM = + "ESP32-DevKit|ESP32 Cam AiThinker"; // Default module settings -const uint8_t kModuleNiceList[MAXMODULE] PROGMEM = { WEMOS }; +const uint8_t kModuleNiceList[MAXMODULE] PROGMEM = { + WEMOS, + ESP32_CAM_AITHINKER +}; -const mytmplt kModules[MAXMODULE] PROGMEM = { - { // WEMOS - Espressif ESP32-DevKitC - Any ESP32 device like WeMos and NodeMCU hardware (ESP32) - GPIO_USER, //0 (I)O GPIO0, ADC2_CH1, TOUCH1, RTC_GPIO11, CLK_OUT1, EMAC_TX_CLK - GPIO_USER, //1 IO TXD0 GPIO1, U0TXD, CLK_OUT3, EMAC_RXD2 - GPIO_USER, //2 IO GPIO2, ADC2_CH2, TOUCH2, RTC_GPIO12, HSPIWP, HS2_DATA0, SD_DATA0 - GPIO_USER, //3 IO RXD0 GPIO3, U0RXD, CLK_OUT2 - GPIO_USER, //4 IO GPIO4, ADC2_CH0, TOUCH0, RTC_GPIO10, HSPIHD, HS2_DATA1, SD_DATA1, EMAC_TX_ER - GPIO_USER, //5 IO GPIO5, VSPICS0, HS1_DATA6, EMAC_RX_CLK - //6 IO GPIO6, Flash CLK - //7 IO GPIO7, Flash D0 - //8 IO GPIO8, Flash D1 - //9 IO GPIO9, Flash D2 - //10 IO GPIO10, Flash D3 - //11 IO GPIO11, Flash CMD - GPIO_USER, //12 (I)O GPIO12, ADC2_CH5, TOUCH5, RTC_GPIO15, MTDI, HSPIQ, HS2_DATA2, SD_DATA2, EMAC_TXD3 (If driven High, flash voltage (VDD_SDIO) is 1.8V not default 3.3V. Has internal pull-down, so unconnected = Low = 3.3V. May prevent flashing and/or booting if 3.3V flash is connected and pulled high. See ESP32 datasheet for more details.) - GPIO_USER, //13 IO GPIO13, ADC2_CH4, TOUCH4, RTC_GPIO14, MTCK, HSPID, HS2_DATA3, SD_DATA3, EMAC_RX_ER - GPIO_USER, //14 IO GPIO14, ADC2_CH6, TOUCH6, RTC_GPIO16, MTMS, HSPICLK, HS2_CLK, SD_CLK, EMAC_TXD2 - GPIO_USER, //15 (I)O GPIO15, ADC2_CH3, TOUCH3, MTDO, HSPICS0, RTC_GPIO13, HS2_CMD, SD_CMD, EMAC_RXD3 (If driven Low, silences boot messages from normal boot. Has internal pull-up, so unconnected = High = normal output.) - GPIO_USER, //16 IO GPIO16, HS1_DATA4, U2RXD, EMAC_CLK_OUT - GPIO_USER, //17 IO GPIO17, HS1_DATA5, U2TXD, EMAC_CLK_OUT_180 - GPIO_USER, //18 IO GPIO18, VSPICLK, HS1_DATA7 - GPIO_USER, //19 IO GPIO19, VSPIQ, U0CTS, EMAC_TXD0 - 0, //20 - GPIO_USER, //21 IO GPIO21, VSPIHD, EMAC_TX_EN - GPIO_USER, //22 IO LED GPIO22, VSPIWP, U0RTS, EMAC_TXD1 - GPIO_USER, //23 IO GPIO23, VSPID, HS1_STROBE - 0, //24 - GPIO_USER, //25 IO GPIO25, DAC_1, ADC2_CH8, RTC_GPIO6, EMAC_RXD0 - GPIO_USER, //26 IO GPIO26, DAC_2, ADC2_CH9, RTC_GPIO7, EMAC_RXD1 - GPIO_USER, //27 IO GPIO27, ADC2_CH7, TOUCH7, RTC_GPIO17, EMAC_RX_DV - 0, //28 - 0, //29 - 0, //30 - 0, //31 - GPIO_USER, //32 IO GPIO32, XTAL_32K_P (32.768 kHz crystal oscillator input), ADC1_CH4, TOUCH9, RTC_GPIO9 - GPIO_USER, //33 IO GPIO33, XTAL_32K_N (32.768 kHz crystal oscillator output), ADC1_CH5, TOUCH8, RTC_GPIO8 - GPIO_USER, //34 I NO PULLUP GPIO34, ADC1_CH6, RTC_GPIO4 - GPIO_USER, //35 I NO PULLUP GPIO35, ADC1_CH7, RTC_GPIO5 - GPIO_USER, //36 I NO PULLUP GPIO36, SENSOR_VP, ADC_H, ADC1_CH0, RTC_GPIO0 - 0, //37 NO PULLUP - 0, //38 NO PULLUP - GPIO_USER //39 I NO PULLUP GPIO39, SENSOR_VN, ADC1_CH3, ADC_H, RTC_GPIO3 - } +const mytmplt kModules PROGMEM = +{ // WEMOS - Espressif ESP32-DevKitC - Any ESP32 device like WeMos and NodeMCU hardware (ESP32) + GPIO_USER, //0 (I)O GPIO0, ADC2_CH1, TOUCH1, RTC_GPIO11, CLK_OUT1, EMAC_TX_CLK + GPIO_USER, //1 IO TXD0 GPIO1, U0TXD, CLK_OUT3, EMAC_RXD2 + GPIO_USER, //2 IO GPIO2, ADC2_CH2, TOUCH2, RTC_GPIO12, HSPIWP, HS2_DATA0, SD_DATA0 + GPIO_USER, //3 IO RXD0 GPIO3, U0RXD, CLK_OUT2 + GPIO_USER, //4 IO GPIO4, ADC2_CH0, TOUCH0, RTC_GPIO10, HSPIHD, HS2_DATA1, SD_DATA1, EMAC_TX_ER + GPIO_USER, //5 IO GPIO5, VSPICS0, HS1_DATA6, EMAC_RX_CLK + //6 IO GPIO6, Flash CLK + //7 IO GPIO7, Flash D0 + //8 IO GPIO8, Flash D1 + //9 IO GPIO9, Flash D2 + //10 IO GPIO10, Flash D3 + //11 IO GPIO11, Flash CMD + GPIO_USER, //12 (I)O GPIO12, ADC2_CH5, TOUCH5, RTC_GPIO15, MTDI, HSPIQ, HS2_DATA2, SD_DATA2, EMAC_TXD3 (If driven High, flash voltage (VDD_SDIO) is 1.8V not default 3.3V. Has internal pull-down, so unconnected = Low = 3.3V. May prevent flashing and/or booting if 3.3V flash is connected and pulled high. See ESP32 datasheet for more details.) + GPIO_USER, //13 IO GPIO13, ADC2_CH4, TOUCH4, RTC_GPIO14, MTCK, HSPID, HS2_DATA3, SD_DATA3, EMAC_RX_ER + GPIO_USER, //14 IO GPIO14, ADC2_CH6, TOUCH6, RTC_GPIO16, MTMS, HSPICLK, HS2_CLK, SD_CLK, EMAC_TXD2 + GPIO_USER, //15 (I)O GPIO15, ADC2_CH3, TOUCH3, MTDO, HSPICS0, RTC_GPIO13, HS2_CMD, SD_CMD, EMAC_RXD3 (If driven Low, silences boot messages from normal boot. Has internal pull-up, so unconnected = High = normal output.) + GPIO_USER, //16 IO GPIO16, HS1_DATA4, U2RXD, EMAC_CLK_OUT + GPIO_USER, //17 IO GPIO17, HS1_DATA5, U2TXD, EMAC_CLK_OUT_180 + GPIO_USER, //18 IO GPIO18, VSPICLK, HS1_DATA7 + GPIO_USER, //19 IO GPIO19, VSPIQ, U0CTS, EMAC_TXD0 + 0, //20 + GPIO_USER, //21 IO GPIO21, VSPIHD, EMAC_TX_EN + GPIO_USER, //22 IO LED GPIO22, VSPIWP, U0RTS, EMAC_TXD1 + GPIO_USER, //23 IO GPIO23, VSPID, HS1_STROBE + 0, //24 + GPIO_USER, //25 IO GPIO25, DAC_1, ADC2_CH8, RTC_GPIO6, EMAC_RXD0 + GPIO_USER, //26 IO GPIO26, DAC_2, ADC2_CH9, RTC_GPIO7, EMAC_RXD1 + GPIO_USER, //27 IO GPIO27, ADC2_CH7, TOUCH7, RTC_GPIO17, EMAC_RX_DV + 0, //28 + 0, //29 + 0, //30 + 0, //31 + GPIO_USER, //32 IO GPIO32, XTAL_32K_P (32.768 kHz crystal oscillator input), ADC1_CH4, TOUCH9, RTC_GPIO9 + GPIO_USER, //33 IO GPIO33, XTAL_32K_N (32.768 kHz crystal oscillator output), ADC1_CH5, TOUCH8, RTC_GPIO8 + GPIO_USER, //34 I NO PULLUP GPIO34, ADC1_CH6, RTC_GPIO4 + GPIO_USER, //35 I NO PULLUP GPIO35, ADC1_CH7, RTC_GPIO5 + GPIO_USER, //36 I NO PULLUP GPIO36, SENSOR_VP, ADC_H, ADC1_CH0, RTC_GPIO0 + 0, //37 NO PULLUP + 0, //38 NO PULLUP + GPIO_USER, //39 I NO PULLUP GPIO39, SENSOR_VN, ADC1_CH3, ADC_H, RTC_GPIO3 + 0 // Flag }; #endif // ESP32 diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 5660a231d..d2e2e1813 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -261,21 +261,18 @@ const char HTTP_SCRIPT_TEMPLATE[] PROGMEM = "as=o.shift();" // Complete ADC0 list "g=o.shift().split(',');" // Array separator "j=0;" -// "for(i=0;i<13;i++){" // Supports 13 GPIOs "for(i=0;i<" STR(MAX_USER_PINS) ";i++){" // Supports 13 GPIOs #ifdef ESP8266 "if(6==i){j=9;}" "if(8==i){j=12;}" -#endif -#ifdef ESP32 +#else // ESP32 "if(6==i){j=12;}" -#endif +#endif // ESP8266 - ESP32 "sk(g[i],j);" // Set GPIO "j++;" "}" "g=o.shift();" // FLAG "os=as;" -// "sk(g&15,17);" // Set ADC0 "sk(g&15," STR(ADC0_PIN) ");" // Set ADC0 "g>>=4;" "for(i=0;i<" STR(GPIO_FLAG_USED) ";i++){" @@ -295,7 +292,6 @@ const char HTTP_SCRIPT_TEMPLATE[] PROGMEM = "function x2(a){" "os=a.responseText;" -// "sk(17,99);" // 17 = WEMOS "sk(" STR(WEMOS_MODULE) ",99);" // 17 = WEMOS "st(" STR(USER_MODULE) ");" "}" @@ -321,13 +317,11 @@ const char HTTP_SCRIPT_MODULE2[] PROGMEM = "}" "function x3(a){" // ADC0 "os=a.responseText;" -// "sk(%d,17);" "sk(%d," STR(ADC0_PIN) ");" "}" "function sl(){" "ld('md?m=1',x1);" // ?m related to Webserver->hasArg("m") "ld('md?g=1',x2);" // ?g related to Webserver->hasArg("g") -// "if(eb('g17')){" "if(eb('g" STR(ADC0_PIN) "')){" "ld('md?a=1',x3);" // ?a related to Webserver->hasArg("a") "}" @@ -1532,10 +1526,9 @@ void TemplateSaveSettings(void) #ifdef ESP8266 if (6 == i) { j = 9; } if (8 == i) { j = 12; } -#endif // ESP8266 -#ifdef ESP32 +#else // ESP32 if (6 == i) { j = 12; } -#endif // ESP32 +#endif // ESP8266 - ESP32 snprintf_P(webindex, sizeof(webindex), PSTR("g%d"), j); WebGetArg(webindex, tmp, sizeof(tmp)); // GPIO uint8_t gpio = atoi(tmp); @@ -1543,7 +1536,6 @@ void TemplateSaveSettings(void) j++; } -// WebGetArg("g17", tmp, sizeof(tmp)); // FLAG - ADC0 WebGetArg("g" STR(ADC0_PIN), tmp, sizeof(tmp)); // FLAG - ADC0 uint32_t flag = atoi(tmp); for (uint32_t i = 0; i < GPIO_FLAG_USED; i++) { From 22e05c2e27e1575db1d62d929fcc9a632aed4354 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 17 Apr 2020 16:15:41 +0200 Subject: [PATCH 12/16] No Map file is generated for ESP32 Fix error for ESP32. Scripts checks if exists and generates only in target folder if there --- pio/name-firmware.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pio/name-firmware.py b/pio/name-firmware.py index dfb9b7f85..1490ecc5c 100644 --- a/pio/name-firmware.py +++ b/pio/name-firmware.py @@ -28,6 +28,7 @@ def bin_map_copy(source, target, env): shutil.copy(str(target[0]), bin_file) # copy firmware.map to map/.map - shutil.copy("firmware.map", map_file) + if os.path.isfile("firmware.map"): + shutil.move("firmware.map", map_file) env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_map_copy]) From 36c9a44512e905978f57c96eb38c7092579b65cc Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 17 Apr 2020 17:14:06 +0200 Subject: [PATCH 13/16] Fix Zigbee DimmerUp/DimmerDown malformed --- tasmota/CHANGELOG.md | 1 + tasmota/xdrv_23_zigbee_6_commands.ino | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 273c44db5..0fb205aff 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,7 @@ ### 8.2.0.4 20200417 - Add config version tag +- Fix Zigbee DimmerUp/DimmerDown malformed ### 8.2.0.3 20200329 diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index 1893925f3..8cb695bd6 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -53,7 +53,7 @@ ZF(DimmerMove) ZF(DimmerStep) ZF(HueMove) ZF(HueStep) ZF(SatMove) ZF(SatStep) ZF(ColorMove) ZF(ColorStep) ZF(ArrowClick) ZF(ArrowHold) ZF(ArrowRelease) ZF(ZoneStatusChange) -ZF(xxxx00) ZF(xxxx) ZF(01xxxx) ZF(00) ZF(01) ZF() ZF(xxxxyy) ZF(001902) ZF(011902) ZF(xxyyyy) ZF(xx) +ZF(xxxx00) ZF(xxxx) ZF(01xxxx) ZF(00) ZF(01) ZF() ZF(xxxxyy) ZF(00190200) ZF(01190200) ZF(xxyyyy) ZF(xx) ZF(xx000A00) ZF(xx0A00) ZF(xxyy0A00) ZF(xxxxyyyy0A00) ZF(xxxx0A00) ZF(xx0A) ZF(xx190A00) ZF(xx19) ZF(xx190A) ZF(xxxxyyyy) ZF(xxxxyyzz) ZF(xxyyzzzz) ZF(xxyyyyzz) @@ -82,8 +82,8 @@ const Z_CommandConverter Z_Commands[] PROGMEM = { // Light & Shutter commands { Z(Power), 0x0006, 0xFF, 0x01, Z() }, // 0=Off, 1=On, 2=Toggle { Z(Dimmer), 0x0008, 0x04, 0x01, Z(xx0A00) }, // Move to Level with On/Off, xx=0..254 (255 is invalid) - { Z(DimmerUp), 0x0008, 0x06, 0x01, Z(001902) }, // Step up by 10%, 0.2 secs - { Z(DimmerDown), 0x0008, 0x06, 0x01, Z(011902) }, // Step down by 10%, 0.2 secs + { Z(DimmerUp), 0x0008, 0x06, 0x01, Z(00190200) }, // Step up by 10%, 0.2 secs + { Z(DimmerDown), 0x0008, 0x06, 0x01, Z(01190200) }, // Step down by 10%, 0.2 secs { Z(DimmerStop), 0x0008, 0x03, 0x01, Z() }, // Stop any Dimmer animation { Z(ResetAlarm), 0x0009, 0x00, 0x01, Z(xxyyyy) }, // Reset alarm (alarm code + cluster identifier) { Z(ResetAllAlarms), 0x0009, 0x01, 0x01, Z() }, // Reset all alarms From fa51f9f4ad70b73cd9a3ccb219891f049bc76e73 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 17 Apr 2020 17:20:46 +0200 Subject: [PATCH 14/16] Add the possibility to add Custom env to build special Tasmota versions with Compiler switches defined in [env:tasmota-xyz] in `platformio_tasmota_cenv.ini` to control user_config_override.h Example: "[env:tasmota-zigbee] build_flags = ${common.build_flags} -DHANS_CONFIG_ZIGBEE=true" which referrs to in user_config_override.h ```#ifdef HANS_CONFIG_ZIGBEE // ****************************************************************** #undef CODE_IMAGE_STR #define CODE_IMAGE_STR "ZIGBEE" #define USE_WEBSERVER // Enable web server and Wifi Manager (+66k code, +8k mem) #define USE_JAVASCRIPT_ES6 // Enable ECMAScript6 syntax using less JavaScript code bytes (fails on IE11) // #define USE_WEBSEND_RESPONSE // Enable command WebSend response message (+1k code) //#define USE_RULES // Add support for rules (+4k4 code) // #define USE_EXPRESSION // Add support for expression evaluation in rules (+3k2 code, +64 bytes mem) // #define SUPPORT_IF_STATEMENT // Add support for IF statement in rules (+4k2 code, -332 bytes mem) // #define SUPPORT_MQTT_EVENT // Support trigger event with MQTT subscriptions (+3k5 code) //#define USE_SCRIPT // Add support for script // #define USE_SCRIPT_FATFS 4 // Add support for script storage on SD card (+12k code, +4k mem) // #define USE_SCRIPT_WEB_DISPLAY #define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices // -- Zigbee interface ---------------------------- #define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP (+35k code, +3.2k mem) #define USE_ZIGBEE_PANID 0x1A63 // arbitrary PAN ID for Zigbee network, must be unique in the home #define USE_ZIGBEE_EXTPANID 0xCCCCCCCCCCCCCCCCL // arbitrary extended PAN ID #define USE_ZIGBEE_CHANNEL 11 // Zigbee Channel (11-26) #define USE_ZIGBEE_PRECFGKEY_L 0x0F0D0B0907050301L // note: changing requires to re-pair all devices #define USE_ZIGBEE_PRECFGKEY_H 0x0D0C0A0806040200L // note: changing requires to re-pair all devices #define USE_ZIGBEE_PERMIT_JOIN false // don't allow joining by default #define USE_ZIGBEE_COALESCE_ATTR_TIMER 350 // timer to coalesce attribute values (in ms) #endif ``` --- platformio_override_sample.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 701f9c119..8e898f6b6 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -12,6 +12,7 @@ [platformio] extra_configs = platformio_tasmota_env32.ini + platformio_tasmota_cenv.ini ; *** Build/upload environment default_envs = From 3f9fdc09ce4d247e85af0626ffc52c0757e8a19d Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 17 Apr 2020 17:52:44 +0200 Subject: [PATCH 15/16] Remove warning/errors when compiling Zigbee for ESP32 --- tasmota/xdrv_23_zigbee_1_headers.ino | 8 ++++---- tasmota/xdrv_23_zigbee_3_hue.ino | 10 ++++------ tasmota/xdrv_23_zigbee_5_converters.ino | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index 051858697..5a250c781 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -25,15 +25,15 @@ void ZigbeeZCLSend_Raw(uint16_t dtsAddr, uint16_t groupaddr, uint16_t clusterId, // Get an JSON attribute, with case insensitive key search -JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) { +const JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) { // key can be in PROGMEM if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) { return *(JsonVariant*)nullptr; } - for (auto kv : json) { - const char *key = kv.key; - JsonVariant &value = kv.value; + for (JsonObject::const_iterator it=json.begin(); it!=json.end(); ++it) { + const char *key = it->key; + const JsonVariant &value = it->value; if (0 == strcasecmp_P(key, needle)) { return value; diff --git a/tasmota/xdrv_23_zigbee_3_hue.ino b/tasmota/xdrv_23_zigbee_3_hue.ino index e534134dc..87b7d39da 100644 --- a/tasmota/xdrv_23_zigbee_3_hue.ino +++ b/tasmota/xdrv_23_zigbee_3_hue.ino @@ -201,12 +201,10 @@ void ZigbeeHandleHue(uint16_t shortaddr, uint32_t device_id, String &response) { PSTR("{\"success\":{\"/lights/%d/state/on\":%s}}"), device_id, on ? "true" : "false"); - switch(on) - { - case false : ZigbeeHuePower(shortaddr, 0x00); - break; - case true : ZigbeeHuePower(shortaddr, 0x01); - break; + if (on) { + ZigbeeHuePower(shortaddr, 0x01); + } else { + ZigbeeHuePower(shortaddr, 0x00); } response += buf; resp = true; diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 1e115d13f..67ff8993f 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -109,7 +109,7 @@ public: uint16_t srcaddr, uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast, uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber, uint32_t timestamp): - _cmd_id(cmd_id), _manuf_code(manuf_code), _transact_seq(transact_seq), + _manuf_code(manuf_code), _transact_seq(transact_seq), _cmd_id(cmd_id), _payload(buf_len ? buf_len : 250), // allocate the data frame from source or preallocate big enough _cluster_id(clusterid), _groupaddr(groupaddr), _srcaddr(srcaddr), _srcendpoint(srcendpoint), _dstendpoint(dstendpoint), _wasbroadcast(wasbroadcast), @@ -215,9 +215,9 @@ private: uint16_t _manuf_code = 0; // optional uint8_t _transact_seq = 0; // transaction sequence number uint8_t _cmd_id = 0; + SBuffer _payload; uint16_t _cluster_id = 0; uint16_t _groupaddr = 0; - SBuffer _payload; // information from decoded ZCL frame uint16_t _srcaddr; uint8_t _srcendpoint; From 8ada81221825c592b8fb5a5ccb16535b29bf958c Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 17 Apr 2020 18:27:31 +0200 Subject: [PATCH 16/16] Add ESP32 optional brownout disable Add ESP32 brownout disable for weak onboard LDO's - eventually you'll need to change the LDO to a better one. --- libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp | 9 +++++++++ libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h | 1 + tasmota/tasmota.ino | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp b/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp index 029d41470..6635f6d4d 100644 --- a/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp +++ b/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp @@ -109,3 +109,12 @@ uint32_t ESP_getSketchSize(void) } return sketchsize; } + +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" + +void DisableBrownout(void) +{ + // https://github.com/espressif/arduino-esp32/issues/863#issuecomment-347179737 + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Disable brownout detector +} diff --git a/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h b/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h index 28e187d52..276772b95 100644 --- a/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h +++ b/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h @@ -43,6 +43,7 @@ uint32_t ESP_getFlashChipId(); uint32_t ESP_getChipId(); String String_ESP_getChipId(); uint32_t ESP_getSketchSize(); +void DisableBrownout(void); // Analog inline void analogWrite(uint8_t pin, int val) diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 1a7c250ae..a0d684517 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -192,6 +192,12 @@ void setup(void) { global_state.data = 3; // Init global state (wifi_down, mqtt_down) to solve possible network issues +#ifdef ESP32 +#ifdef DISABLE_BROWNOUT + DisableBrownout(); +#endif +#endif + RtcRebootLoad(); if (!RtcRebootValid()) { RtcReboot.fast_reboot_count = 0;