From ee25e6e637fbe26bf7c7eba61ab99a894fcfa34a Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Thu, 14 May 2020 22:44:32 +0200 Subject: [PATCH 01/21] Bugfix and autotune added (disabled by define, as experimental and untested) --- tasmota/my_user_config.h | 5 + tasmota/xdrv_39_thermostat.ino | 408 ++++++++++++++++++++++++++++++--- 2 files changed, 380 insertions(+), 33 deletions(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 2e3eb4a5f..4f9ffdc82 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -696,6 +696,11 @@ #define THERMOSTAT_TIME_OUTPUT_DELAY 180 // Default output delay between state change and real actuation event (f.i. valve open/closed) #define THERMOSTAT_TEMP_INIT 180 // Default init target temperature for the thermostat controller #define THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST 3 // Default maximum time where the input and the outpus shall differ (for diagnostic) in minutes + #define THERMOSTAT_TIME_MAX_AUTOTUNE 21600 // Maximum time for the PI autotune function to complete in seconds + #define THERMOSTAT_DUTYCYCLE_AUTOTUNE 35 // Default duty cycle (in % over PI cycle time) for the step response of the autotune PI function + #define THERMOSTAT_PEAKNUMBER_AUTOTUNE 8 // Default number of peak temperatures (max or min) to be used for the autotune PI function + #define THERMOSTAT_TEMP_BAND_NO_PEAK_DET 1 // Default temperature band in thenths of degrees celsius within no peak will be detected + #define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful // -- End of general directives ------------------- diff --git a/tasmota/xdrv_39_thermostat.ino b/tasmota/xdrv_39_thermostat.ino index 5301d9ca2..05928c210 100644 --- a/tasmota/xdrv_39_thermostat.ino +++ b/tasmota/xdrv_39_thermostat.ino @@ -24,6 +24,9 @@ // Enable/disable debugging //#define DEBUG_THERMOSTAT +// Enable/disable experimental PI auto-tuning +//#define USE_PI_AUTOTUNING // (Ziegler-Nichols closed loop method) + #ifdef DEBUG_THERMOSTAT #define DOMOTICZ_MAX_IDX 4 #define DOMOTICZ_IDX1 791 @@ -56,6 +59,9 @@ #define D_CMND_TIMEPICYCLESET "TimePiCycleSet" #define D_CMND_TEMPANTIWINDUPRESETSET "TempAntiWindupResetSet" #define D_CMND_TEMPHYSTSET "TempHystSet" +#ifdef USE_PI_AUTOTUNING +#define D_CMND_PERFLEVELAUTOTUNE "PerfLevelAutotune" +#endif // USE_PI_AUTOTUNING #define D_CMND_TIMEMAXACTIONSET "TimeMaxActionSet" #define D_CMND_TIMEMINACTIONSET "TimeMinActionSet" #define D_CMND_TIMEMINTURNOFFACTIONSET "TimeMinTurnoffActionSet" @@ -70,8 +76,15 @@ #define D_CMND_DIAGNOSTICMODESET "DiagnosticModeSet" enum ThermostatModes { THERMOSTAT_OFF, THERMOSTAT_AUTOMATIC_OP, THERMOSTAT_MANUAL_OP, THERMOSTAT_MODES_MAX }; +#ifdef USE_PI_AUTOTUNING +enum ControllerModes { CTR_HYBRID, CTR_PI, CTR_RAMP_UP, CTR_PI_AUTOTUNE, CTR_MODES_MAX }; +enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP, CTR_HYBRID_PI, CTR_HYBRID_PI_AUTOTUNE }; +enum AutotuneStates { AUTOTUNE_OFF, AUTOTUNE_ON, AUTOTUNE_MAX }; +enum AutotunePerformanceParam { AUTOTUNE_PERF_FAST, AUTOTUNE_PERF_NORMAL, AUTOTUNE_PERF_SLOW, AUTOTUNE_PERF_MAX }; +#else enum ControllerModes { CTR_HYBRID, CTR_PI, CTR_RAMP_UP, CTR_MODES_MAX }; enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP, CTR_HYBRID_PI }; +#endif // USE_PI_AUTOTUNING enum ClimateModes { CLIMATE_HEATING, CLIMATE_COOLING, CLIMATE_MODES_MAX }; enum InterfaceStates { IFACE_OFF, IFACE_ON }; enum InputUsage { INPUT_NOT_USED, INPUT_USED }; @@ -113,17 +126,29 @@ typedef union { uint32_t status_output : 1; // Flag stating state of the output (0 = inactive, 1 = active) uint32_t status_input : 1; // Flag stating state of the input (0 = inactive, 1 = active) uint32_t use_input : 1; // Flag stating if the input switch shall be used to switch to manual mode - uint32_t phase_hybrid_ctr : 1; // Phase of the hybrid controller (Ramp-up or PI) + uint32_t phase_hybrid_ctr : 2; // Phase of the hybrid controller (Ramp-up, PI or Autotune) uint32_t status_cycle_active : 1; // Status showing if cycle is active (Output ON) or not (Output OFF) - uint32_t state_emergency : 1; // State for thermostat emergency uint32_t counter_seconds : 6; // Second counter used to track minutes uint32_t output_relay_number : 4; // Output relay number uint32_t input_switch_number : 3; // Input switch number - uint32_t output_inconsist_ctr : 2; // Counter of the minutes where the output state is inconsistent with the command - uint32_t diagnostic_mode : 1; // Diagnostic mode selected - uint32_t free : 1; // Free bits in Bitfield +#ifdef USE_PI_AUTOTUNING + uint32_t autotune_flag : 1; // Enable/disable autotune + uint32_t autotune_perf_mode : 2; // Autotune performance mode + uint32_t free : 1; // Free bits +#else + uint32_t free : 4; // Free bits +#endif // USE_PI_AUTOTUNING }; -} ThermostatBitfield; +} ThermostatStateBitfield; + +typedef union { + uint8_t data; + struct { + uint8_t state_emergency : 1; // State for thermostat emergency + uint8_t diagnostic_mode : 1; // Diagnostic mode selected + uint8_t output_inconsist_ctr : 2; // Counter of the minutes where the output state is inconsistent with the command + }; +} ThermostatDiagBitfield; #ifdef DEBUG_THERMOSTAT const char DOMOTICZ_MES[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\"}"; @@ -135,28 +160,36 @@ const char kThermostatCommands[] PROGMEM = "|" D_CMND_THERMOSTATMODESET "|" D_CM D_CMND_OUTPUTRELAYSET "|" D_CMND_TIMEALLOWRAMPUPSET "|" D_CMND_TEMPFORMATSET "|" D_CMND_TEMPMEASUREDSET "|" D_CMND_TEMPTARGETSET "|" D_CMND_TEMPMEASUREDGRDREAD "|" D_CMND_SENSORINPUTSET "|" D_CMND_STATEEMERGENCYSET "|" D_CMND_TIMEMANUALTOAUTOSET "|" D_CMND_PROPBANDSET "|" D_CMND_TIMERESETSET "|" D_CMND_TIMEPICYCLESET "|" - D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|" D_CMND_TIMEMINACTIONSET "|" - D_CMND_TIMEMINTURNOFFACTIONSET "|" D_CMND_TEMPRUPDELTINSET "|" D_CMND_TEMPRUPDELTOUTSET "|" D_CMND_TIMERAMPUPMAXSET "|" - D_CMND_TIMERAMPUPCYCLESET "|" D_CMND_TEMPRAMPUPPIACCERRSET "|" D_CMND_TIMEPIPROPORTREAD "|" D_CMND_TIMEPIINTEGRREAD "|" - D_CMND_TIMESENSLOSTSET "|" D_CMND_DIAGNOSTICMODESET; +#ifdef USE_PI_AUTOTUNING + D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_PERFLEVELAUTOTUNE "|" D_CMND_TIMEMAXACTIONSET "|" +#else + D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|" +#endif // USE_PI_AUTOTUNING + D_CMND_TIMEMINACTIONSET "|" D_CMND_TIMEMINTURNOFFACTIONSET "|" D_CMND_TEMPRUPDELTINSET "|" D_CMND_TEMPRUPDELTOUTSET "|" + D_CMND_TIMERAMPUPMAXSET "|" D_CMND_TIMERAMPUPCYCLESET "|" D_CMND_TEMPRAMPUPPIACCERRSET "|" D_CMND_TIMEPIPROPORTREAD "|" + D_CMND_TIMEPIINTEGRREAD "|" D_CMND_TIMESENSLOSTSET "|" D_CMND_DIAGNOSTICMODESET; void (* const ThermostatCommand[])(void) PROGMEM = { &CmndThermostatModeSet, &CmndClimateModeSet, &CmndTempFrostProtectSet, &CmndControllerModeSet, &CmndInputSwitchSet, &CmndInputSwitchUse, &CmndOutputRelaySet, &CmndTimeAllowRampupSet, &CmndTempFormatSet, &CmndTempMeasuredSet, &CmndTempTargetSet, &CmndTempMeasuredGrdRead, &CmndSensorInputSet, &CmndStateEmergencySet, &CmndTimeManualToAutoSet, &CmndPropBandSet, &CmndTimeResetSet, &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, &CmndTempHystSet, +#ifdef USE_PI_AUTOTUNING + &CmndPerfLevelAutotune, &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet, +#else &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet, +#endif // USE_PI_AUTOTUNING &CmndTempRupDeltOutSet, &CmndTimeRampupMaxSet, &CmndTimeRampupCycleSet, &CmndTempRampupPiAccErrSet, &CmndTimePiProportRead, &CmndTimePiIntegrRead, &CmndTimeSensLostSet, &CmndDiagnosticModeSet }; struct THERMOSTAT { - ThermostatBitfield status; // Bittfield including states as well as several flags + ThermostatStateBitfield status; // Bittfield including states as well as several flags uint32_t timestamp_temp_measured_update = 0; // Timestamp of latest measurement update uint32_t timestamp_temp_meas_change_update = 0; // Timestamp of latest measurement value change (> or < to previous) uint32_t timestamp_output_off = 0; // Timestamp of latest thermostat output Off state uint32_t timestamp_input_on = 0; // Timestamp of latest input On state uint32_t time_thermostat_total = 0; // Time thermostat on within a specific timeframe - uint32_t time_ctr_checkpoint = 0; // Time to finalize the control cycle within the PI strategy or to switch to PI from Rampup + uint32_t time_ctr_checkpoint = 0; // Time to finalize the control cycle within the PI strategy or to switch to PI from Rampup in seconds uint32_t time_ctr_changepoint = 0; // Time until switching off output within the controller in seconds int32_t temp_measured_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees per hour int16_t temp_target_level = THERMOSTAT_TEMP_INIT; // Target level of the thermostat in tenths of degrees @@ -167,7 +200,7 @@ struct THERMOSTAT { int32_t time_integral_pi; // Time integral part of the PI controller int32_t time_total_pi; // Time total (proportional + integral) of the PI controller uint16_t kP_pi = 0; // kP value for the PI controller multiplied by 100 (to avoid floating point operations) - uint16_t kI_pi = 0; // kP value for the PI controller multiplied by 100 (to avoid floating point operations) + uint16_t kI_pi = 0; // kI value for the PI controller multiplied by 100 (to avoid floating point operations) int32_t temp_rampup_meas_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees celsius per hour calculated during ramp-up uint32_t timestamp_rampup_start = 0; // Timestamp where the ramp-up controller mode has been started uint32_t time_rampup_deadtime = 0; // Time constant of the thermostat system (step response time) @@ -195,6 +228,23 @@ struct THERMOSTAT { uint8_t temp_reset_anti_windup = THERMOSTAT_TEMP_RESET_ANTI_WINDUP; // Range where reset antiwindup is disabled, in tenths of degrees celsius int8_t temp_hysteresis = THERMOSTAT_TEMP_HYSTERESIS; // Range hysteresis for temperature PI controller, in tenths of degrees celsius uint8_t temp_frost_protect = THERMOSTAT_TEMP_FROST_PROTECT; // Minimum temperature for frost protection, in tenths of degrees celsius + ThermostatDiagBitfield diag; // Bittfield including diagnostic flags +#ifdef USE_PI_AUTOTUNING + uint8_t dutycycle_step_autotune = THERMOSTAT_DUTYCYCLE_AUTOTUNE; // Duty cycle for the step response of the autotune PI function in % + uint8_t peak_ctr = 0; // Peak counter for the autotuning function + uint8_t temp_band_no_peak_det = THERMOSTAT_TEMP_BAND_NO_PEAK_DET; // Temperature band in thenths of degrees celsius within no peak will be detected + uint8_t val_prop_band_atune = 0; // Proportional band calculated from the the PI autotune function in degrees celsius + uint32_t time_reset_atune = 0; // Reset time calculated from the PI autotune function in seconds + uint16_t pU_pi_atune = 0; // pU value ("Ultimate" period) period of self-sustaining oscillations determined when the controller gain was set to Ku in minutes (for PI autotune) + uint16_t kU_pi_atune = 0; // kU value ("Ultimate" gain) determined by increasing controller gain until self-sustaining oscillations are achieved (for PI autotune) + uint16_t kP_pi_atune = 0; // kP value calculated by the autotune PI function multiplied by 100 (to avoid floating point operations) + uint16_t kI_pi_atune = 0; // kI value calulated by the autotune PI function multiplied by 100 (to avoid floating point operations) + int16_t temp_peaks_atune[THERMOSTAT_PEAKNUMBER_AUTOTUNE]; // Array to store temperature peaks to be used by the autotune PI function + int16_t temp_abs_max_atune; // Max temperature reached within autotune + int16_t temp_abs_min_atune; // Min temperature reached within autotune + uint16_t time_peak_timestamps_atune[THERMOSTAT_PEAKNUMBER_AUTOTUNE]; // Array to store timestamps in minutes of the temperature peaks to be used by the autotune PI function + uint16_t time_std_dev_peak_det_ok = THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK; // Standard deviation in minutes of the oscillation periods within the peak detection is successful +#endif // USE_PI_AUTOTUNING } Thermostat[THERMOSTAT_CONTROLLER_OUTPUTS]; /*********************************************************************************************/ @@ -212,13 +262,17 @@ void ThermostatInit(uint8_t ctr_output) Thermostat[ctr_output].status.status_output = IFACE_OFF; Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_PI; Thermostat[ctr_output].status.status_cycle_active = CYCLE_OFF; - Thermostat[ctr_output].status.state_emergency = EMERGENCY_OFF; + Thermostat[ctr_output].diag.state_emergency = EMERGENCY_OFF; Thermostat[ctr_output].status.counter_seconds = 0; Thermostat[ctr_output].status.output_relay_number = (THERMOSTAT_RELAY_NUMBER + ctr_output); Thermostat[ctr_output].status.input_switch_number = (THERMOSTAT_SWITCH_NUMBER + ctr_output); Thermostat[ctr_output].status.use_input = INPUT_NOT_USED; - Thermostat[ctr_output].status.output_inconsist_ctr = 0; - Thermostat[ctr_output].status.diagnostic_mode = DIAGNOSTIC_ON; + Thermostat[ctr_output].diag.output_inconsist_ctr = 0; + Thermostat[ctr_output].diag.diagnostic_mode = DIAGNOSTIC_ON; +#ifdef USE_PI_AUTOTUNING + Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF; + Thermostat[ctr_output].status.autotune_perf_mode = AUTOTUNE_PERF_FAST; +#endif // USE_PI_AUTOTUNING // Make sure the Output is OFF ExecuteCommandPower(Thermostat[ctr_output].status.output_relay_number, POWER_OFF, SRC_THERMOSTAT); } @@ -312,10 +366,10 @@ void ThermostatSignalPostProcessingSlow(uint8_t ctr_output) { // Increate counter when inconsistent output state exists if (Thermostat[ctr_output].status.status_output != Thermostat[ctr_output].status.command_output) { - Thermostat[ctr_output].status.output_inconsist_ctr++; + Thermostat[ctr_output].diag.output_inconsist_ctr++; } else { - Thermostat[ctr_output].status.output_inconsist_ctr = 0; + Thermostat[ctr_output].diag.output_inconsist_ctr = 0; } } @@ -333,6 +387,10 @@ void ThermostatSignalProcessingFast(uint8_t ctr_output) void ThermostatCtrState(uint8_t ctr_output) { +#ifdef USE_PI_AUTOTUNING + bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING); +#endif //USE_PI_AUTOTUNING + switch (Thermostat[ctr_output].status.controller_mode) { // Hybrid controller (Ramp-up + PI) case CTR_HYBRID: @@ -340,10 +398,35 @@ void ThermostatCtrState(uint8_t ctr_output) break; // PI controller case CTR_PI: +#ifdef USE_PI_AUTOTUNING + // If Autotune has been enabled (via flag) + // AND we have just reached the setpoint temperature + // AND the temperature gradient is negative for heating and positive for cooling + // then switch state to PI autotuning + if ((Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_ON) + &&(Thermostat[ctr_output].temp_measured == Thermostat[ctr_output].temp_target_level) + && ((flag_heating && (Thermostat[ctr_output].temp_measured_gradient < 0)) + ||(!flag_heating && (Thermostat[ctr_output].temp_measured_gradient > 0)))) + { + Thermostat[ctr_output].status.controller_mode = CTR_PI_AUTOTUNE; + ThermostatPeakDetectorInit(ctr_output); + } +#endif // USE_PI_AUTOTUNING break; // Ramp-up controller (predictive) case CTR_RAMP_UP: break; +#ifdef USE_PI_AUTOTUNING + // PI autotune + case CTR_PI_AUTOTUNE: + // If autotune finalized (flag Off) + // then go back to the PI controller + if (Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_OFF) + { + Thermostat[ctr_output].status.controller_mode = CTR_PI; + } + break; +#endif //USE_PI_AUTOTUNING } } @@ -387,7 +470,32 @@ void ThermostatHybridCtrPhase(uint8_t ctr_output) Thermostat[ctr_output].time_ctr_checkpoint = 0; Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_RAMP_UP; } +#ifdef USE_PI_AUTOTUNING + // If Autotune has been enabled (via flag) + // AND we have just reached the setpoint temperature + // AND the temperature gradient is negative for heating and positive for cooling + // then switch state to PI autotuning + if ((Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_ON) + &&(Thermostat[ctr_output].temp_measured == Thermostat[ctr_output].temp_target_level) + && ((flag_heating && (Thermostat[ctr_output].temp_measured_gradient < 0)) + ||(!flag_heating && (Thermostat[ctr_output].temp_measured_gradient > 0)))) + { + Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_PI_AUTOTUNE; + ThermostatPeakDetectorInit(ctr_output); + } +#endif // USE_PI_AUTOTUNING break; +#ifdef USE_PI_AUTOTUNING + // PI autotune controller phase + case CTR_HYBRID_PI_AUTOTUNE: + // If autotune finalized (flag Off) + // then go back to the PI controller + if (Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_OFF) + { + Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_PI; + } + break; +#endif // USE_PI_AUTOTUNING } } #ifdef DEBUG_THERMOSTAT @@ -830,6 +938,202 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output) } } +#ifdef USE_PI_AUTOTUNING + +void ThermostatPeakDetectorInit(uint8_t ctr_output) +{ + for (uint8_t i = 0; i < THERMOSTAT_PEAKNUMBER_AUTOTUNE; i++) { + Thermostat[ctr_output].temp_peaks_atune[i] = 0; + } + Thermostat[ctr_output].pU_pi_atune = 0; + Thermostat[ctr_output].kP_pi_atune = 0; + Thermostat[ctr_output].kI_pi_atune = 0; + Thermostat[ctr_output].kU_pi_atune = 0; + Thermostat[ctr_output].peak_ctr = 0; + Thermostat[ctr_output].temp_abs_max_atune = 0; + Thermostat[ctr_output].temp_abs_min_atune = 100; + Thermostat[ctr_output].time_ctr_checkpoint = uptime + THERMOSTAT_TIME_MAX_AUTOTUNE; +} + +void ThermostatPeakDetector(uint8_t ctr_output) +{ + uint8_t peak_num = Thermostat[ctr_output].peak_ctr; + int16_t peak_avg = 0; + bool peak_transition = false; + // Update Max/Min Thermostat[ctr_output].temp_abs_max_atune + if (Thermostat[ctr_output].temp_measured > Thermostat[ctr_output].temp_abs_max_atune) { + Thermostat[ctr_output].temp_abs_max_atune = Thermostat[ctr_output].temp_measured; + } + if (Thermostat[ctr_output].temp_measured < Thermostat[ctr_output].temp_abs_min_atune) { + Thermostat[ctr_output].temp_abs_min_atune = Thermostat[ctr_output].temp_measured; + } + // For heating, even peak numbers look for maxes, odd for minds, the contrary for cooling + // If we did not found all peaks yet + if (peak_num < THERMOSTAT_PEAKNUMBER_AUTOTUNE) { + bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING); + bool cond_peak_1 = ( (Thermostat[ctr_output].temp_measured > Thermostat[ctr_output].temp_peaks_atune[peak_num]) + && (flag_heating) + || (Thermostat[ctr_output].temp_measured < Thermostat[ctr_output].temp_peaks_atune[peak_num]) + && (!flag_heating)); + bool cond_peak_2 = ( (Thermostat[ctr_output].temp_measured < Thermostat[ctr_output].temp_peaks_atune[peak_num]) + && (flag_heating) + || (Thermostat[ctr_output].temp_measured > Thermostat[ctr_output].temp_peaks_atune[peak_num]) + && (!flag_heating)); + bool cond_gradient_1 = ( (Thermostat[ctr_output].temp_measured_gradient > 0) + && (flag_heating) + || (Thermostat[ctr_output].temp_measured_gradient < 0) + && (!flag_heating)); + bool cond_gradient_2 = ( (Thermostat[ctr_output].temp_measured_gradient < 0) + && (flag_heating) + || (Thermostat[ctr_output].temp_measured_gradient > 0) + && (!flag_heating)); + // If peak number is even (look for max if heating and min if cooling) + if ((peak_num % 2) == 0) { + // If current temperature higher (heating) or lower (cooling) than registered value for peak + // AND temperature gradient > 0 for heating or < 0 for cooling + // then, update value + if (cond_peak_1 && cond_gradient_1) { + Thermostat[ctr_output].temp_peaks_atune[peak_num] = Thermostat[ctr_output].temp_measured; + } + // Else if current temperature lower (heating) or higher (cooling) then registered value for peak + // AND difference to peak is outside of the peak no detection band + // then the current peak value is the peak (max for heating, min for cooling), switch detection + if ( (cond_peak_2) + && (abs(Thermostat[ctr_output].temp_measured - Thermostat[ctr_output].temp_peaks_atune[peak_num]) > Thermostat[ctr_output].temp_band_no_peak_det)) { + // Register peak timestamp; + Thermostat[ctr_output].time_peak_timestamps_atune[peak_num] = (uptime / 60); + Thermostat[ctr_output].peak_ctr++; + peak_transition = true; + } + } + // Peak number is odd (look for min if heating and max if cooling) + else { + // If current temperature lower (heating) or higher (cooling) than registered value for peak + // AND temperature gradient < 0 for heating or > 0 for cooling + // then, update value + if (cond_peak_2 && cond_gradient_2) { + Thermostat[ctr_output].temp_peaks_atune[peak_num] = Thermostat[ctr_output].temp_measured; + } + // Else if current temperature higher (heating) or lower (cooling) then registered value for peak + // AND difference to peak is outside of the peak no detection band + // then the current peak value is the peak (min for heating, max for cooling), switch detection + if ( (cond_peak_1) + && (abs(Thermostat[ctr_output].temp_measured - Thermostat[ctr_output].temp_peaks_atune[peak_num]) > Thermostat[ctr_output].temp_band_no_peak_det)) { + // Calculate period + // Register peak timestamp; + Thermostat[ctr_output].time_peak_timestamps_atune[peak_num] = (uptime / 60); + Thermostat[ctr_output].peak_ctr++; + peak_transition = true; + } + } + } + else { + // Peak detection done, proceed to evaluate results + ThermostatAutotuneParamCalc(ctr_output); + } + + // If peak detection not finalized but bigger than 3 and we have just found a peak, check if results can be extracted + if ((Thermostat[ctr_output].peak_ctr > 2) && (peak_transition)) { + //Update peak_num + peak_num = Thermostat[ctr_output].peak_ctr; + // Calculate average value among the last 3 peaks + peak_avg = (abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 1] + - Thermostat[ctr_output].temp_peaks_atune[peak_num - 2]) + + abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 2] + - Thermostat[ctr_output].temp_peaks_atune[peak_num - 3])) / 2; + + if ((20 * (int32_t)peak_avg) < (int32_t)(Thermostat[ctr_output].temp_abs_max_atune - Thermostat[ctr_output].temp_abs_min_atune)) { + // Calculate average temperature among all peaks + for (uint8_t i = 0; i < peak_num; i++) { + peak_avg += Thermostat[ctr_output].temp_peaks_atune[i]; + } + peak_avg /= peak_num; + // If last period crosses the average value, result valid + if (10 * abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 1] - Thermostat[ctr_output].temp_peaks_atune[peak_num - 2]) < (Thermostat[ctr_output].temp_abs_max_atune - peak_avg)) { + // Peak detection done, proceed to evaluate results + ThermostatAutotuneParamCalc(ctr_output); + } + } + } + peak_transition = false; +} + +void ThermostatAutotuneParamCalc(uint8_t ctr_output) +{ + uint8_t peak_num = Thermostat[ctr_output].peak_ctr; + + // Calculate the tunning parameters + // Resolution increased to avoid float operations + Thermostat[ctr_output].kU_pi_atune = (uint16_t)(100 * ((uint32_t)400000 * (uint32_t)(Thermostat[ctr_output].dutycycle_step_autotune)) / ((uint32_t)(Thermostat[ctr_output].temp_abs_max_atune - Thermostat[ctr_output].temp_abs_min_atune) * (uint32_t)314159)); + Thermostat[ctr_output].pU_pi_atune = (Thermostat[ctr_output].time_peak_timestamps_atune[peak_num - 1] - Thermostat[ctr_output].time_peak_timestamps_atune[peak_num - 2]); + + switch (Thermostat[ctr_output].status.autotune_perf_mode) { + case AUTOTUNE_PERF_FAST: + // Calculate kP/Ki autotune + Thermostat[ctr_output].kP_pi_atune = (4 * Thermostat[ctr_output].kU_pi_atune) / 10; + break; + case AUTOTUNE_PERF_NORMAL: + // Calculate kP/Ki autotune + Thermostat[ctr_output].kP_pi_atune = (18 * Thermostat[ctr_output].kU_pi_atune) / 100; + break; + case AUTOTUNE_PERF_SLOW: + // Calculate kP/Ki autotune + Thermostat[ctr_output].kP_pi_atune = (13 * Thermostat[ctr_output].kU_pi_atune) / 100; + break; + } + + // Resolution increased to avoid float operations + Thermostat[ctr_output].kI_pi_atune = (12 * (6000 * Thermostat[ctr_output].kU_pi_atune / Thermostat[ctr_output].pU_pi_atune)) / 10; + + // Calculate PropBand Autotune + Thermostat[ctr_output].val_prop_band_atune = 100 / Thermostat[ctr_output].kP_pi_atune; + // Calculate Reset Time Autotune + Thermostat[ctr_output].time_reset_atune = (uint32_t)((((uint32_t)Thermostat[ctr_output].kP_pi_atune * (uint32_t)Thermostat[ctr_output].time_pi_cycle * 6000)) / (uint32_t)Thermostat[ctr_output].kI_pi_atune); +} + +void ThermostatWorkAutomaticPIAutotune(uint8_t ctr_output) +{ + bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING); + // If no timeout of the PI Autotune function + if (uptime < Thermostat[ctr_output].time_ctr_checkpoint) { + if (uptime >= Thermostat[ctr_output].time_ctr_checkpoint) { + Thermostat[ctr_output].temp_target_level_ctr = Thermostat[ctr_output].temp_target_level; + // Calculate time_ctr_changepoint + Thermostat[ctr_output].time_ctr_changepoint = uptime + (((uint32_t)Thermostat[ctr_output].time_pi_cycle * (uint32_t)Thermostat[ctr_output].dutycycle_step_autotune) / (uint32_t)100); + // Reset cycle active + Thermostat[ctr_output].status.status_cycle_active = CYCLE_OFF; + } + // Set Output On/Off depending on the changepoint + if (uptime < Thermostat[ctr_output].time_ctr_changepoint) { + Thermostat[ctr_output].status.status_cycle_active = CYCLE_ON; + Thermostat[ctr_output].status.command_output = IFACE_ON; + } + else { + Thermostat[ctr_output].status.command_output = IFACE_OFF; + } + // Update peak values + ThermostatPeakDetector(ctr_output); + } + else { + // Disable Autotune flag + Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF; + } + + if (Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_OFF) { + // Set output Off + Thermostat[ctr_output].status.command_output = IFACE_OFF; + } + + // Evaluate if kU, pU can be calculated + + // Output conditions: + // If Thermostat[ctr_output].temp_target_level_ctr != Thermostat[ctr_output].temp_target_level -> Disable Autotune Flag + // If timeout (check which existing variable to use) -> Disable Autotune flag + // If calculation of Kp_autotune & Ki_autotune done -> Disable Autotune flag + // Before starting call ThermostatPeakDetectorInit() +} +#endif //USE_PI_AUTOTUNING + void ThermostatCtrWork(uint8_t ctr_output) { switch (Thermostat[ctr_output].status.controller_mode) { @@ -842,6 +1146,12 @@ void ThermostatCtrWork(uint8_t ctr_output) case CTR_HYBRID_PI: ThermostatWorkAutomaticPI(ctr_output); break; +#ifdef USE_PI_AUTOTUNING + // PI autotune + case CTR_HYBRID_PI_AUTOTUNE: + ThermostatWorkAutomaticPIAutotune(ctr_output); + break; +#endif //USE_PI_AUTOTUNING } break; // PI controller @@ -852,6 +1162,12 @@ void ThermostatCtrWork(uint8_t ctr_output) case CTR_RAMP_UP: ThermostatWorkAutomaticRampUp(ctr_output); break; +#ifdef USE_PI_AUTOTUNING + // PI autotune + case CTR_PI_AUTOTUNE: + ThermostatWorkAutomaticPIAutotune(ctr_output); + break; +#endif //USE_PI_AUTOTUNING } } @@ -879,17 +1195,17 @@ void ThermostatWork(uint8_t ctr_output) void ThermostatDiagnostics(uint8_t ctr_output) { // Diagnostic related to the plausibility of the output state - if ((Thermostat[ctr_output].status.diagnostic_mode == DIAGNOSTIC_ON) - &&(Thermostat[ctr_output].status.output_inconsist_ctr >= THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST)) { + if ((Thermostat[ctr_output].diag.diagnostic_mode == DIAGNOSTIC_ON) + &&(Thermostat[ctr_output].diag.output_inconsist_ctr >= THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST)) { Thermostat[ctr_output].status.thermostat_mode = THERMOSTAT_OFF; - Thermostat[ctr_output].status.state_emergency = EMERGENCY_ON; + Thermostat[ctr_output].diag.state_emergency = EMERGENCY_ON; } // Diagnostic related to the plausibility of the output power implemented // already into the energy driver // If diagnostics fail, emergency enabled and thermostat shutdown triggered - if (Thermostat[ctr_output].status.state_emergency == EMERGENCY_ON) { + if (Thermostat[ctr_output].diag.state_emergency == EMERGENCY_ON) { ThermostatEmergencyShutdown(ctr_output); } } @@ -947,10 +1263,10 @@ void ThermostatDebug(uint8_t ctr_output) AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.counter_seconds: %s"), result_chr); dtostrfd(Thermostat[ctr_output].status.thermostat_mode, 0, result_chr); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.thermostat_mode: %s"), result_chr); - dtostrfd(Thermostat[ctr_output].status.state_emergency, 0, result_chr); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.state_emergency: %s"), result_chr); - dtostrfd(Thermostat[ctr_output].status.output_inconsist_ctr, 0, result_chr); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.output_inconsist_ctr: %s"), result_chr); + dtostrfd(Thermostat[ctr_output].diag.state_emergency, 0, result_chr); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].diag.state_emergency: %s"), result_chr); + dtostrfd(Thermostat[ctr_output].diag.output_inconsist_ctr, 0, result_chr); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].diag.output_inconsist_ctr: %s"), result_chr); dtostrfd(Thermostat[ctr_output].status.controller_mode, 0, result_chr); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.controller_mode: %s"), result_chr); dtostrfd(Thermostat[ctr_output].status.command_output, 0, result_chr); @@ -1049,6 +1365,8 @@ void CmndClimateModeSet(void) uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data)); if ((value >= CLIMATE_HEATING) && (value < CLIMATE_MODES_MAX)) { Thermostat[ctr_output].status.climate_mode = value; + // Trigger a restart of the controller + Thermostat[ctr_output].time_ctr_checkpoint = uptime; } } ResponseCmndNumber((int)Thermostat[ctr_output].status.climate_mode); @@ -1090,6 +1408,14 @@ void CmndControllerModeSet(void) uint8_t value = (uint8_t)(XdrvMailbox.payload); if ((value >= CTR_HYBRID) && (value < CTR_MODES_MAX)) { Thermostat[ctr_output].status.controller_mode = value; + // Reset controller variables + Thermostat[ctr_output].timestamp_rampup_start = uptime; + Thermostat[ctr_output].temp_rampup_start = Thermostat[ctr_output].temp_measured; + Thermostat[ctr_output].temp_rampup_meas_gradient = 0; + Thermostat[ctr_output].time_rampup_deadtime = 0; + Thermostat[ctr_output].counter_rampup_cycles = 1; + Thermostat[ctr_output].time_ctr_changepoint = 0; + Thermostat[ctr_output].time_ctr_checkpoint = 0; } } ResponseCmndNumber((int)Thermostat[ctr_output].status.controller_mode); @@ -1156,11 +1482,11 @@ void CmndTimeAllowRampupSet(void) uint8_t ctr_output = XdrvMailbox.index - 1; if (XdrvMailbox.data_len > 0) { uint32_t value = (uint32_t)(XdrvMailbox.payload); - if ((value >= 0) && (value < 86400)) { - Thermostat[ctr_output].time_allow_rampup = (uint16_t)(value / 60); + if ((value >= 0) && (value < 1440)) { + Thermostat[ctr_output].time_allow_rampup = (uint16_t)value; } } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_allow_rampup * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_allow_rampup)); } } @@ -1266,10 +1592,10 @@ void CmndStateEmergencySet(void) if (XdrvMailbox.data_len > 0) { uint8_t value = (uint8_t)(XdrvMailbox.payload); if ((value >= 0) && (value <= 1)) { - Thermostat[ctr_output].status.state_emergency = (uint16_t)value; + Thermostat[ctr_output].diag.state_emergency = (uint16_t)value; } } - ResponseCmndNumber((int)Thermostat[ctr_output].status.state_emergency); + ResponseCmndNumber((int)Thermostat[ctr_output].diag.state_emergency); } } @@ -1399,6 +1725,22 @@ void CmndTempHystSet(void) } } +#ifdef USE_PI_AUTOTUNING +void CmndPerfLevelAutotune(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { + uint8_t ctr_output = XdrvMailbox.index - 1; + if (XdrvMailbox.data_len > 0) { + uint8_t value = (uint8_t)(XdrvMailbox.payload); + if ((value >= 0) && (value <= AUTOTUNE_PERF_MAX)) { + Thermostat[ctr_output].status.autotune_perf_mode = value; + } + } + ResponseCmndNumber((int)Thermostat[ctr_output].status.autotune_perf_mode); + } +} +#endif // USE_PI_AUTOTUNING + void CmndTimeMaxActionSet(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { @@ -1571,10 +1913,10 @@ void CmndDiagnosticModeSet(void) if (XdrvMailbox.data_len > 0) { uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data)); if ((value >= DIAGNOSTIC_OFF) && (value <= DIAGNOSTIC_ON)) { - Thermostat[ctr_output].status.diagnostic_mode = value; + Thermostat[ctr_output].diag.diagnostic_mode = value; } } - ResponseCmndNumber((int)Thermostat[ctr_output].status.diagnostic_mode); + ResponseCmndNumber((int)Thermostat[ctr_output].diag.diagnostic_mode); } } From b1fd316876b3aa42293d7e0a76cb6a651719229d Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Thu, 14 May 2020 23:35:45 +0200 Subject: [PATCH 02/21] Further dev. of PI autotuning --- tasmota/xdrv_39_thermostat.ino | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tasmota/xdrv_39_thermostat.ino b/tasmota/xdrv_39_thermostat.ino index 05928c210..440b3c52a 100644 --- a/tasmota/xdrv_39_thermostat.ino +++ b/tasmota/xdrv_39_thermostat.ino @@ -1030,6 +1030,7 @@ void ThermostatPeakDetector(uint8_t ctr_output) else { // Peak detection done, proceed to evaluate results ThermostatAutotuneParamCalc(ctr_output); + Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF; } // If peak detection not finalized but bigger than 3 and we have just found a peak, check if results can be extracted @@ -1052,6 +1053,7 @@ void ThermostatPeakDetector(uint8_t ctr_output) if (10 * abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 1] - Thermostat[ctr_output].temp_peaks_atune[peak_num - 2]) < (Thermostat[ctr_output].temp_abs_max_atune - peak_avg)) { // Peak detection done, proceed to evaluate results ThermostatAutotuneParamCalc(ctr_output); + Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF; } } } @@ -1095,7 +1097,9 @@ void ThermostatWorkAutomaticPIAutotune(uint8_t ctr_output) { bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING); // If no timeout of the PI Autotune function - if (uptime < Thermostat[ctr_output].time_ctr_checkpoint) { + // AND no change in setpoint + if ((uptime < Thermostat[ctr_output].time_ctr_checkpoint) + &&(Thermostat[ctr_output].temp_target_level_ctr == Thermostat[ctr_output].temp_target_level)) { if (uptime >= Thermostat[ctr_output].time_ctr_checkpoint) { Thermostat[ctr_output].temp_target_level_ctr = Thermostat[ctr_output].temp_target_level; // Calculate time_ctr_changepoint @@ -1123,14 +1127,6 @@ void ThermostatWorkAutomaticPIAutotune(uint8_t ctr_output) // Set output Off Thermostat[ctr_output].status.command_output = IFACE_OFF; } - - // Evaluate if kU, pU can be calculated - - // Output conditions: - // If Thermostat[ctr_output].temp_target_level_ctr != Thermostat[ctr_output].temp_target_level -> Disable Autotune Flag - // If timeout (check which existing variable to use) -> Disable Autotune flag - // If calculation of Kp_autotune & Ki_autotune done -> Disable Autotune flag - // Before starting call ThermostatPeakDetectorInit() } #endif //USE_PI_AUTOTUNING From 187bc9d23e2b2510e3e75b477e208ba3d68026d6 Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Thu, 14 May 2020 23:42:01 +0200 Subject: [PATCH 03/21] Added source reference for Autotune --- tasmota/xdrv_39_thermostat.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasmota/xdrv_39_thermostat.ino b/tasmota/xdrv_39_thermostat.ino index 440b3c52a..8471bed40 100644 --- a/tasmota/xdrv_39_thermostat.ino +++ b/tasmota/xdrv_39_thermostat.ino @@ -24,7 +24,8 @@ // Enable/disable debugging //#define DEBUG_THERMOSTAT -// Enable/disable experimental PI auto-tuning +// Enable/disable experimental PI auto-tuning inspired by the Arduino Autotune Library by +// Brett Beauregard brettbeauregard.com //#define USE_PI_AUTOTUNING // (Ziegler-Nichols closed loop method) #ifdef DEBUG_THERMOSTAT From 6573802f83ef5a46c68c7abdbe1ca56f06b98c94 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 15 May 2020 14:07:16 +0200 Subject: [PATCH 04/21] Shrink knx code size by 5k using local pow --- .../esp-knx-ip-conversion.cpp | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/esp-knx-ip-0.5.2/esp-knx-ip-conversion.cpp b/lib/esp-knx-ip-0.5.2/esp-knx-ip-conversion.cpp index 9dc2fd563..02f2f59be 100644 --- a/lib/esp-knx-ip-0.5.2/esp-knx-ip-conversion.cpp +++ b/lib/esp-knx-ip-0.5.2/esp-knx-ip-conversion.cpp @@ -35,12 +35,36 @@ uint16_t ESPKNXIP::data_to_2byte_uint(uint8_t *data) return (uint16_t)((data[1] << 8) | data[2]); } +float esp_knx_pow(float a, float b) +{ + // https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/ + // calculate approximation with fraction of the exponent + int e = abs((int)b); + union { + double d; + int x[2]; + } u = { a }; + u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447); + u.x[0] = 0; + // exponentiation by squaring with the exponent's integer part + // double r = u.d makes everything much slower, not sure why + double r = 1.0; + while (e) { + if (e & 1) { + r *= a; + } + a *= a; + e >>= 1; + } + return r * u.d; +} + float ESPKNXIP::data_to_2byte_float(uint8_t *data) { //uint8_t sign = (data[1] & 0b10000000) >> 7; uint8_t expo = (data[1] & 0b01111000) >> 3; int16_t mant = ((data[1] & 0b10000111) << 8) | data[2]; - return 0.01f * mant * pow(2, expo); + return 0.01f * mant * esp_knx_pow(2, expo); } time_of_day_t ESPKNXIP::data_to_3byte_time(uint8_t *data) From bafe8a137f4623544e4f2120fb24d6d5fbf66b84 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 15 May 2020 14:17:11 +0200 Subject: [PATCH 05/21] Shrink sensors code size by 5k using local pow --- .../src/MutichannelGasSensor.cpp | 166 ++++++++++-------- 1 file changed, 95 insertions(+), 71 deletions(-) diff --git a/lib/Mutichannel_Gas_Sensor/src/MutichannelGasSensor.cpp b/lib/Mutichannel_Gas_Sensor/src/MutichannelGasSensor.cpp index 9be9473ef..66e62fa4a 100644 --- a/lib/Mutichannel_Gas_Sensor/src/MutichannelGasSensor.cpp +++ b/lib/Mutichannel_Gas_Sensor/src/MutichannelGasSensor.cpp @@ -57,7 +57,7 @@ bool MutichannelGasSensor::isError() unsigned char MutichannelGasSensor::getVersion() { - if(get_addr_dta(CMD_READ_EEPROM, ADDR_IS_SET) == 1126) // get version + if(get_addr_dta(CMD_READ_EEPROM, ADDR_IS_SET) == 1126) // get version { __version = 2; return __version; @@ -105,66 +105,66 @@ START: return 0; } } - + Wire.requestFrom(i2cAddress, (uint8_t)2); - + unsigned int dta = 0; - + unsigned char raw[10]; int cnt = 0; - + while(Wire.available()) { raw[cnt++] = Wire.read(); } - + if(cnt == 0)goto START; dta = raw[0]; dta <<= 8; dta += raw[1]; - + switch(addr_reg) { case CH_VALUE_NH3: - + if(dta > 0) { adcValueR0_NH3_Buf = dta; } - else + else { dta = adcValueR0_NH3_Buf; } - + break; - + case CH_VALUE_CO: - + if(dta > 0) { adcValueR0_CO_Buf = dta; } - else + else { dta = adcValueR0_CO_Buf; } - + break; - + case CH_VALUE_NO2: - + if(dta > 0) { adcValueR0_NO2_Buf = dta; } - else + else { dta = adcValueR0_NO2_Buf; } - + break; - + default:; } return dta; @@ -173,7 +173,7 @@ START: unsigned int MutichannelGasSensor::get_addr_dta(unsigned char addr_reg, unsigned char __dta) { int trys = 0; -START: +START: __send_error = false; Wire.beginTransmission(i2cAddress); Wire.write(addr_reg); @@ -185,25 +185,25 @@ START: return 0; } } - + Wire.requestFrom(i2cAddress, (uint8_t)2); - + unsigned int dta = 0; - + unsigned char raw[10]; int cnt = 0; - + while(Wire.available()) { raw[cnt++] = Wire.read(); } - + if(cnt == 0)goto START; dta = raw[0]; dta <<= 8; dta += raw[1]; - + return dta; } @@ -269,7 +269,7 @@ int16_t MutichannelGasSensor::readR0(void) int16_t rtnData = 0; rtnData = readData(0x11); - + if(rtnData > 0) res0[0] = rtnData; else @@ -327,6 +327,30 @@ int16_t MutichannelGasSensor::readR(void) ** Returns: float value - concentration of the gas *********************************************************************************************************/ +float MutichannelGasSensor_pow(float a, float b) +{ + // https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/ + // calculate approximation with fraction of the exponent + int e = abs((int)b); + union { + double d; + int x[2]; + } u = { a }; + u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447); + u.x[0] = 0; + // exponentiation by squaring with the exponent's integer part + // double r = u.d makes everything much slower, not sure why + double r = 1.0; + while (e) { + if (e & 1) { + r *= a; + } + a *= a; + e >>= 1; + } + return r * u.d; +} + float MutichannelGasSensor::calcGas(int gas) { @@ -338,7 +362,7 @@ float MutichannelGasSensor::calcGas(int gas) if(readR0() >= 0) r0_inited = true; else return -1.0f; } - + if(readR() < 0) return -2.0f; @@ -353,65 +377,65 @@ float MutichannelGasSensor::calcGas(int gas) int A0_0 = get_addr_dta(6, ADDR_USER_ADC_HN3); int A0_1 = get_addr_dta(6, ADDR_USER_ADC_CO); int A0_2 = get_addr_dta(6, ADDR_USER_ADC_NO2); - + int An_0 = get_addr_dta(CH_VALUE_NH3); int An_1 = get_addr_dta(CH_VALUE_CO); int An_2 = get_addr_dta(CH_VALUE_NO2); - + ratio0 = (float)An_0/(float)A0_0*(1023.0-A0_0)/(1023.0-An_0); ratio1 = (float)An_1/(float)A0_1*(1023.0-A0_1)/(1023.0-An_1); ratio2 = (float)An_2/(float)A0_2*(1023.0-A0_2)/(1023.0-An_2); - + } - + float c = 0; switch(gas) { case CO: { - c = pow(ratio1, -1.179)*4.385; //mod by jack + c = MutichannelGasSensor_pow(ratio1, -1.179)*4.385; //mod by jack break; } case NO2: { - c = pow(ratio2, 1.007)/6.855; //mod by jack + c = MutichannelGasSensor_pow(ratio2, 1.007)/6.855; //mod by jack break; } case NH3: { - c = pow(ratio0, -1.67)/1.47; //modi by jack + c = MutichannelGasSensor_pow(ratio0, -1.67)/1.47; //modi by jack break; } case C3H8: //add by jack { - c = pow(ratio0, -2.518)*570.164; + c = MutichannelGasSensor_pow(ratio0, -2.518)*570.164; break; } case C4H10: //add by jack { - c = pow(ratio0, -2.138)*398.107; + c = MutichannelGasSensor_pow(ratio0, -2.138)*398.107; break; } case GAS_CH4: //add by jack { - c = pow(ratio1, -4.363)*630.957; + c = MutichannelGasSensor_pow(ratio1, -4.363)*630.957; break; } case H2: //add by jack { - c = pow(ratio1, -1.8)*0.73; + c = MutichannelGasSensor_pow(ratio1, -1.8)*0.73; break; } case C2H5OH: //add by jack { - c = pow(ratio1, -1.552)*1.622; + c = MutichannelGasSensor_pow(ratio1, -1.552)*1.622; break; } default: break; } - + if(2==__version)ledOff(); return isnan(c)?-3:c; } @@ -474,7 +498,7 @@ void MutichannelGasSensor::doCalibrate(void) a0 = get_addr_dta(CH_VALUE_NH3); a1 = get_addr_dta(CH_VALUE_CO); a2 = get_addr_dta(CH_VALUE_NO2); - + Serial.print(a0); Serial.print('\t'); Serial.print(a1); @@ -482,44 +506,44 @@ void MutichannelGasSensor::doCalibrate(void) Serial.print(a2); Serial.println('\t'); ledOn(); - + int cnt = 0; for(i=0; i<20; i++) { if((a0 - get_addr_dta(CH_VALUE_NH3)) > 2 || (get_addr_dta(CH_VALUE_NH3) - a0) > 2)cnt++; if((a1 - get_addr_dta(CH_VALUE_CO)) > 2 || (get_addr_dta(CH_VALUE_CO) - a1) > 2)cnt++; if((a2 - get_addr_dta(CH_VALUE_NO2)) > 2 || (get_addr_dta(CH_VALUE_NO2) - a2) > 2)cnt++; - + if(cnt>5) { break; } delay(1000); } - + ledOff(); if(cnt <= 5)break; delay(200); } - + Serial.print("write user adc value: "); Serial.print(a0);Serial.print('\t'); Serial.print(a1);Serial.print('\t'); Serial.print(a2);Serial.println('\t'); - + unsigned char tmp[7]; - + tmp[0] = 7; tmp[1] = a0>>8; tmp[2] = a0&0xff; - + tmp[3] = a1>>8; tmp[4] = a1&0xff; tmp[5] = a2>>8; tmp[6] = a2&0xff; - + write_i2c(i2cAddress, tmp, 7); } } @@ -563,7 +587,7 @@ void MutichannelGasSensor::display_eeprom() Serial.println("ERROR: display_eeprom() is NOT support by V1 firmware."); return ; } - + Serial.print("ADDR_IS_SET = "); Serial.println(get_addr_dta(CMD_READ_EEPROM, ADDR_IS_SET)); Serial.print("ADDR_FACTORY_ADC_NH3 = "); Serial.println(get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_NH3)); Serial.print("ADDR_FACTORY_ADC_CO = "); Serial.println(get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_CO)); @@ -581,7 +605,7 @@ float MutichannelGasSensor::getR0(unsigned char ch) // 0:CH3, 1:CO, 2:NO Serial.println("ERROR: getR0() is NOT support by V1 firmware."); return -1; } - + int a = 0; switch(ch) { @@ -590,19 +614,19 @@ float MutichannelGasSensor::getR0(unsigned char ch) // 0:CH3, 1:CO, 2:NO Serial.print("a_ch3 = "); Serial.println(a); break; - + case 1: // CO a = get_addr_dta(CMD_READ_EEPROM, ADDR_USER_ADC_CO); Serial.print("a_co = "); Serial.println(a); break; - + case 2: // NO2 a = get_addr_dta(CMD_READ_EEPROM, ADDR_USER_ADC_NO2); Serial.print("a_no2 = "); Serial.println(a); break; - + default:; } @@ -612,31 +636,31 @@ float MutichannelGasSensor::getR0(unsigned char ch) // 0:CH3, 1:CO, 2:NO float MutichannelGasSensor::getRs(unsigned char ch) // 0:CH3, 1:CO, 2:NO2 { - + if(__version == 1) { Serial.println("ERROR: getRs() is NOT support by V1 firmware."); return -1; } - + int a = 0; switch(ch) { case 0: // NH3 a = get_addr_dta(1); break; - + case 1: // CO a = get_addr_dta(2); break; - + case 2: // NO2 a = get_addr_dta(3); break; - + default:; } - + float r = 56.0*(float)a/(1023.0-(float)a); return r; } @@ -645,12 +669,12 @@ float MutichannelGasSensor::getRs(unsigned char ch) // 0:CH3, 1:CO, 2:NO // 2. change adc value of R0 to default void MutichannelGasSensor::factory_setting() { - + unsigned char tmp[7]; unsigned char error; unsigned char address = 0; - + for(address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of @@ -661,11 +685,11 @@ void MutichannelGasSensor::factory_setting() if (error == 0) { // change i2c to 0x04 - + Serial.print("I2C address is: 0x"); Serial.println(address, HEX); Serial.println("Change I2C address to 0x04"); - + dta_test[0] = CMD_CHANGE_I2C; dta_test[1] = 0x04; write_i2c(address, dta_test, 2); @@ -680,15 +704,15 @@ void MutichannelGasSensor::factory_setting() unsigned int a0 = get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_NH3); unsigned int a1 = get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_CO); unsigned int a2 = get_addr_dta(CMD_READ_EEPROM, ADDR_FACTORY_ADC_NO2); - + tmp[0] = 7; tmp[1] = a0>>8; - tmp[2] = a0&0xff; + tmp[2] = a0&0xff; tmp[3] = a1>>8; tmp[4] = a1&0xff; tmp[5] = a2>>8; - tmp[6] = a2&0xff; + tmp[6] = a2&0xff; delay(100); write_i2c(i2cAddress, tmp, 7); delay(100); @@ -699,13 +723,13 @@ void MutichannelGasSensor::change_i2c_address(unsigned char addr) dta_test[0] = CMD_CHANGE_I2C; dta_test[1] = addr; write_i2c(i2cAddress, dta_test, 2); - - + + Serial.print("FUNCTION: CHANGE I2C ADDRESS: 0X"); Serial.print(i2cAddress, HEX); Serial.print(" > 0x"); Serial.println(addr, HEX); - + i2cAddress = addr; } From 1bbc77e6a7b1c8e1671c7f975a3365e1accbad8e Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Fri, 15 May 2020 15:30:32 +0200 Subject: [PATCH 06/21] sml update fix SML ESP32 uart handling add replace cmd vars to SML section add * option to SML WeBGUI scripter add optional ws2812 array offset scripter negative for next loops --- tasmota/xdrv_10_scripter.ino | 55 +++++++++---- tasmota/xsns_53_sml.ino | 154 ++++++++++++++++++++++++++++++----- 2 files changed, 173 insertions(+), 36 deletions(-) diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 44207609d..624237ab6 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -641,13 +641,14 @@ char *script; #ifdef USE_LIGHT #ifdef USE_WS2812 -void ws2812_set_array(float *array ,uint8_t len) { +void ws2812_set_array(float *array ,uint32_t len, uint32_t offset) { Ws2812ForceSuspend(); - for (uint8_t cnt=0;cntSettings.light_pixels) break; + for (uint32_t cnt=0;cntSettings.light_pixels) break; uint32_t col=array[cnt]; - Ws2812SetColor(cnt+1,col>>16,col>>8,col,0); + Ws2812SetColor(index+1,col>>16,col>>8,col,0); } Ws2812ForceUpdate(); } @@ -2444,7 +2445,7 @@ char *ForceStringVar(char *lp,char *dstr) { } // replace vars in cmd %var% -void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) { +void Replace_Cmd_Vars(char *srcbuf,uint32_t srcsize, char *dstbuf,uint32_t dstsize) { char *cp; uint16_t count; uint8_t vtype; @@ -2455,6 +2456,7 @@ void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) { char string[SCRIPT_MAXSSIZE]; dstsize-=2; for (count=0;count0) { // for next loop *cv_count+=cv_inc; - if (*cv_count<=cv_max) { - lp=cv_ptr; + if (floop==1) { + if (*cv_count<=cv_max) { + lp=cv_ptr; + } else { + lp+=4; + floop=0; + } } else { - lp+=4; - floop=0; + if (*cv_count>=cv_max) { + lp=cv_ptr; + } else { + lp+=4; + floop=0; + } } } @@ -3010,6 +3025,12 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { lp+=7; lp=isvar(lp,&vtype,&ind,0,0,0); if (vtype!=VAR_NV) { + SCRIPT_SKIP_SPACES + if (*lp!=')') { + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + } else { + fvar=0; + } // found variable as result uint8_t index=glob_script_mem.type[ind.index].index; if ((vtype&STYPE)==0) { @@ -3018,7 +3039,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { uint8_t len=0; float *fa=Get_MFAddr(index,&len); //Serial.printf(">> 2 %d\n",(uint32_t)*fa); - if (fa && len) ws2812_set_array(fa,len); + if (fa && len) ws2812_set_array(fa,len,fvar); } } } @@ -3069,7 +3090,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { //AddLog_P(LOG_LEVEL_INFO, tmp); // replace vars in cmd char *tmp=cmdmem+SCRIPT_CMDMEM/2; - Replace_Cmd_Vars(cmd,tmp,SCRIPT_CMDMEM/2); + Replace_Cmd_Vars(cmd,0,tmp,SCRIPT_CMDMEM/2); //toSLog(tmp); if (!strncmp(tmp,"print",5) || pflg) { @@ -4198,7 +4219,7 @@ void Script_Check_Hue(String *response) { } cp++; } - Replace_Cmd_Vars(line,tmp,sizeof(tmp)); + Replace_Cmd_Vars(line,0,tmp,sizeof(tmp)); // check for hue defintions // NAME, TYPE , vars cp=tmp; @@ -5043,7 +5064,7 @@ void ScriptWebShow(char mc) { WSContentSend_PD(SCRIPT_MSG_NUMINP,label,minstr,maxstr,stepstr,vstr,vname); } else { - Replace_Cmd_Vars(lin,tmp,sizeof(tmp)); + Replace_Cmd_Vars(lin,0,tmp,sizeof(tmp)); if (optflg) { WSContentSend_PD(PSTR("
%s
"),tmp); } else { @@ -5052,7 +5073,7 @@ void ScriptWebShow(char mc) { } } else { if (*lin==mc) { - Replace_Cmd_Vars(lin+1,tmp,sizeof(tmp)); + Replace_Cmd_Vars(lin+1,0,tmp,sizeof(tmp)); WSContentSend_PD(PSTR("%s"),tmp); } } @@ -5097,7 +5118,7 @@ uint8_t msect=Run_Scripter(">m",-2,0); } cp++; } - Replace_Cmd_Vars(line,tmp,sizeof(tmp)); + Replace_Cmd_Vars(line,0,tmp,sizeof(tmp)); //client->println(tmp); func(tmp); } @@ -5142,7 +5163,7 @@ void ScriptJsonAppend(void) { } cp++; } - Replace_Cmd_Vars(line,tmp,sizeof(tmp)); + Replace_Cmd_Vars(line,0,tmp,sizeof(tmp)); ResponseAppend_P(PSTR("%s"),tmp); } if (*lp==SCRIPT_EOL) { diff --git a/tasmota/xsns_53_sml.ino b/tasmota/xsns_53_sml.ino index cb08856b1..c63478b48 100755 --- a/tasmota/xsns_53_sml.ino +++ b/tasmota/xsns_53_sml.ino @@ -45,7 +45,9 @@ #include // use special no wait serial driver, should be always on +#ifndef ESP32 #define SPECIAL_SS +#endif // addresses a bug in meter DWS74 //#define DWS74_BUG @@ -440,7 +442,9 @@ const uint8_t meter[]= #endif // max number of meters , may be adjusted +#ifndef MAX_METERS #define MAX_METERS 5 +#endif double meter_vars[SML_MAX_VARS]; // calulate deltas #define MAX_DVARS MAX_METERS*2 @@ -453,7 +457,11 @@ const uint8_t *meter_p; uint8_t meter_spos[MAX_METERS]; // software serial pointers +#ifdef ESP32 +HardwareSerial *meter_ss[MAX_METERS]; +#else TasmotaSerial *meter_ss[MAX_METERS]; +#endif // serial buffers, may be made larger depending on telegram lenght #define SML_BSIZ 48 @@ -774,18 +782,21 @@ uint8_t dump2log=0; bool Serial_available() { uint8_t num=dump2log&7; if (num<1 || num>meters_used) num=1; + if (!meter_ss[num-1]) return 0; return meter_ss[num-1]->available(); } uint8_t Serial_read() { uint8_t num=dump2log&7; if (num<1 || num>meters_used) num=1; + if (!meter_ss[num-1]) return 0; return meter_ss[num-1]->read(); } uint8_t Serial_peek() { uint8_t num=dump2log&7; if (num<1 || num>meters_used) num=1; + if (!meter_ss[num-1]) return 0; return meter_ss[num-1]->peek(); } @@ -1186,7 +1197,8 @@ void sml_shift_in(uint32_t meters,uint32_t shard) { } else if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='M') { smltbuf[meters][meter_spos[meters]] = iob; meter_spos[meters]++; - if (meter_spos[meters]>=9) { + uint32_t mlen=smltbuf[meters][2]+5; + if (meter_spos[meters]>=mlen) { SML_Decode(meters); sml_empty_receiver(meters); meter_spos[meters]=0; @@ -1236,6 +1248,7 @@ uint32_t meters; for (meters=0; metersavailable()) { sml_shift_in(meters,0); } @@ -1530,9 +1543,10 @@ void SML_Decode(uint8_t index) { if (mb_index!=meter_desc_p[mindex].index) { goto nextsect; } - uint16_t crc = MBUS_calculateCRC(&smltbuf[mindex][0],7); - if (lowByte(crc)!=smltbuf[mindex][7]) goto nextsect; - if (highByte(crc)!=smltbuf[mindex][8]) goto nextsect; + uint16_t pos = smltbuf[mindex][2]+3; + uint16_t crc = MBUS_calculateCRC(&smltbuf[mindex][0],pos); + if (lowByte(crc)!=smltbuf[mindex][pos]) goto nextsect; + if (highByte(crc)!=smltbuf[mindex][pos+1]) goto nextsect; dval=mbus_dval; //AddLog_P2(LOG_LEVEL_INFO, PSTR(">> %s"),mp); mp++; @@ -1728,7 +1742,7 @@ void SML_Show(boolean json) { } else { // web ui export //snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s{s}%s %s: {m}%s %s{e}", b_mqtt_data,meter_desc[mindex].prefix,name,tpowstr,unit); - WSContentSend_PD(PSTR("{s}%s %s: {m}%s %s{e}"),meter_desc_p[mindex].prefix,name,tpowstr,unit); + if (strcmp(name,"*")) WSContentSend_PD(PSTR("{s}%s %s: {m}%s %s{e}"),meter_desc_p[mindex].prefix,name,tpowstr,unit); } } } @@ -1830,6 +1844,52 @@ uint8_t *script_meter; #define METER_DEF_SIZE 3000 #endif + + +#ifdef SML_REPLACE_VARS + +#define SML_SRCBSIZE 256 + +uint32_t SML_getlinelen(char *lp) { +uint32_t cnt; + for (cnt=0; cnt') break; + if (*lp==0) break; + } + //AddLog_P2(LOG_LEVEL_INFO, PSTR("len=%d"),mlen); + return mlen+32; +} +#else +uint32_t SML_getscriptsize(char *lp) { + uint32_t mlen=0; + for (uint32_t cnt=0;cntM",-2,0); if (meter_script==99) { // use script definition @@ -1886,13 +1955,7 @@ void SML_Init(void) { lp+=2; meters_used=strtol(lp,0,10); section=1; - uint32_t mlen=0; - for (uint32_t cnt=0;cnt>",lp); + // add meters line -1,1-0:1.8.0*255(@10000,H2OIN,cbm,COUNTER,4| + if (*lp1=='-') lp1++; + uint8_t mnum=strtol(lp1,0,10); + if (mnum<1 || mnum>meters_used) goto next_line; + while (1) { + if (*lp1==0) { + *tp++='|'; + goto next_line; + } + *tp++=*lp1++; + index++; + if (index>=METER_DEF_SIZE) break; + } + } +#else + if (*lp=='-' || isdigit(*lp)) { //toLogEOL(">>",lp); // add meters line -1,1-0:1.8.0*255(@10000,H2OIN,cbm,COUNTER,4| @@ -1997,6 +2084,7 @@ dddef_exit: if (index>=METER_DEF_SIZE) break; } } +#endif } @@ -2013,6 +2101,7 @@ next_line: meter_desc_p=script_meter_desc; meter_p=script_meter; } + } #endif init10: @@ -2024,6 +2113,7 @@ init10: RtcSettings.pulse_counter[i]=Settings.pulse_counter[i]; sml_counters[i].sml_cnt_last_ts=millis(); } + uint32_t uart_index=2; for (uint8_t meters=0; metersbegin(meter_desc_p[meters].params, SERIAL_8E1,meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin); + } else { + meter_ss[meters]->begin(meter_desc_p[meters].params,SERIAL_8N1,meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin); + } +#else if (meter_ss[meters]->begin(meter_desc_p[meters].params)) { meter_ss[meters]->flush(); } @@ -2069,8 +2173,9 @@ init10: Serial.begin(meter_desc_p[meters].params, SERIAL_8E1); } ClaimSerial(); + //Serial.setRxBufferSize(512); } - +#endif } } @@ -2262,6 +2367,11 @@ void SML_Send_Seq(uint32_t meter,char *seq) { uint8_t sbuff[32]; uint8_t *ucp=sbuff,slen=0; char *cp=seq; + uint8_t rflg = 0; + if (*cp=='r') { + rflg = 1; + cp++; + } while (*cp) { if (!*cp || !*(cp+1)) break; if (*cp==',') break; @@ -2272,8 +2382,10 @@ void SML_Send_Seq(uint32_t meter,char *seq) { if (slen>=sizeof(sbuff)) break; } if (script_meter_desc[meter].type=='m' || script_meter_desc[meter].type=='M') { - *ucp++=0; - *ucp++=2; + if (!rflg) { + *ucp++=0; + *ucp++=2; + } // append crc uint16_t crc = MBUS_calculateCRC(sbuff,6); *ucp++=lowByte(crc); @@ -2412,14 +2524,18 @@ bool Xsns53(byte function) { break; case FUNC_LOOP: SML_Counter_Poll(); - break; - case FUNC_EVERY_50_MSECOND: if (dump2log) Dump2log(); else SML_Poll(); break; + // case FUNC_EVERY_50_MSECOND: + // if (dump2log) Dump2log(); + // else SML_Poll(); + // break; #ifdef USE_SCRIPT case FUNC_EVERY_100_MSECOND: - SML_Check_Send(); + if (bitRead(Settings.rule_enabled, 0)) { + SML_Check_Send(); + } break; #endif // USE_SCRIPT case FUNC_JSON_APPEND: From d7bcfefa31f8de7d38b6eaf3cb9e966dfb4f070a Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 15 May 2020 22:10:02 +0200 Subject: [PATCH 07/21] Size code reduction for XY calculations --- tasmota/xdrv_04_light.ino | 87 ++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index 345951696..79e0510ad 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -836,28 +836,44 @@ void LightStateClass::HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t * #define POW FastPrecisePowf +// +// Matrix 3x3 multiplied to a 3 vector, result in a 3 vector +// +void mat3x3(const float *mat33, const float *vec3, float *res3) { + for (uint32_t i = 0; i < 3; i++) { + const float * v = vec3; + *res3 = 0.0f; + for (uint32_t j = 0; j < 3; j++) { + *res3 += *mat33++ * *v++; + } + res3++; + } +} + void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y) { float x = 0.31271f; // default medium white float y = 0.32902f; if (i_r + i_b + i_g > 0) { - float r = (float)i_r / 255.0f; - float g = (float)i_g / 255.0f; - float b = (float)i_b / 255.0f; + float rgb[3] = { (float)i_r, (float)i_g, (float)i_b }; // https://gist.github.com/popcorn245/30afa0f98eea1c2fd34d // Gamma correction - r = (r > 0.04045f) ? POW((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f); - g = (g > 0.04045f) ? POW((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f); - b = (b > 0.04045f) ? POW((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f); + for (uint32_t i = 0; i < 3; i++) { + rgb[i] = rgb[i] / 255.0f; + rgb[i] = (rgb[i] > 0.04045f) ? POW((rgb[i] + 0.055f) / (1.0f + 0.055f), 2.4f) : (rgb[i] / 12.92f); + } // conversion to X, Y, Z // Y is also the Luminance - float X = r * 0.649926f + g * 0.103455f + b * 0.197109f; - float Y = r * 0.234327f + g * 0.743075f + b * 0.022598f; - float Z = r * 0.000000f + g * 0.053077f + b * 1.035763f; + float XYZ[3]; + static const float XYZ_factors[] = { 0.649926f, 0.103455f, 0.197109f, + 0.234327f, 0.743075f, 0.022598f, + 0.000000f, 0.053077f, 1.035763f }; + mat3x3(XYZ_factors, rgb, XYZ); - x = X / (X + Y + Z); - y = Y / (X + Y + Z); + float XYZ_sum = XYZ[0] + XYZ[1] + XYZ[2]; + x = XYZ[0] / XYZ_sum; + y = XYZ[1] / XYZ_sum; // we keep the raw gamut, one nice thing could be to convert to a narrower gamut } if (r_x) *r_x = x; @@ -866,36 +882,33 @@ void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, void LightStateClass::XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb) { + float XYZ[3], rgb[3]; x = (x > 0.99f ? 0.99f : (x < 0.01f ? 0.01f : x)); y = (y > 0.99f ? 0.99f : (y < 0.01f ? 0.01f : y)); float z = 1.0f - x - y; - //float Y = 1.0f; - float X = x / y; - float Z = z / y; - // float r = X * 1.4628067f - 0.1840623f - Z * 0.2743606f; - // float g = -X * 0.5217933f + 1.4472381f + Z * 0.0677227f; - // float b = X * 0.0349342f - 0.0968930f + Z * 1.2884099f; - float r = X * 3.2406f - 1.5372f - Z * 0.4986f; - float g = -X * 0.9689f + 1.8758f + Z * 0.0415f; - float b = X * 0.0557f - 0.2040f + Z * 1.0570f; - float max = (r > g && r > b) ? r : (g > b) ? g : b; - r = r / max; // normalize to max == 1.0 - g = g / max; - b = b / max; - r = (r <= 0.0031308f) ? 12.92f * r : 1.055f * POW(r, (1.0f / 2.4f)) - 0.055f; - g = (g <= 0.0031308f) ? 12.92f * g : 1.055f * POW(g, (1.0f / 2.4f)) - 0.055f; - b = (b <= 0.0031308f) ? 12.92f * b : 1.055f * POW(b, (1.0f / 2.4f)) - 0.055f; - // - // AddLog_P2(LOG_LEVEL_DEBUG_MORE, "XyToRgb XZ (%s %s) rgb (%s %s %s)", - // String(X,5).c_str(), String(Z,5).c_str(), - // String(r,5).c_str(), String(g,5).c_str(),String(b,5).c_str()); + XYZ[0] = x / y; + XYZ[1] = 1.0f; + XYZ[2] = z / y; - int32_t ir = r * 255.0f + 0.5f; - int32_t ig = g * 255.0f + 0.5f; - int32_t ib = b * 255.0f + 0.5f; - if (rr) { *rr = (ir > 255 ? 255: (ir < 0 ? 0 : ir)); } - if (rg) { *rg = (ig > 255 ? 255: (ig < 0 ? 0 : ig)); } - if (rb) { *rb = (ib > 255 ? 255: (ib < 0 ? 0 : ib)); } + static const float rgb_factors[] = { 3.2406f, -1.5372f, -0.4986f, + -0.9689f, 1.8758f, 0.0415f, + 0.0557f, -0.2040f, 1.0570f }; + mat3x3(rgb_factors, XYZ, rgb); + float max = (rgb[0] > rgb[1] && rgb[0] > rgb[2]) ? rgb[0] : (rgb[1] > rgb[2]) ? rgb[1] : rgb[2]; + + for (uint32_t i = 0; i < 3; i++) { + rgb[i] = rgb[i] / max; // normalize to max == 1.0 + rgb[i] = (rgb[i] <= 0.0031308f) ? 12.92f * rgb[i] : 1.055f * POW(rgb[i], (1.0f / 2.4f)) - 0.055f; // gamma + } + + int32_t irgb[3]; + for (uint32_t i = 0; i < 3; i++) { + irgb[i] = rgb[i] * 255.0f + 0.5f; + } + + if (rr) { *rr = (irgb[0] > 255 ? 255: (irgb[0] < 0 ? 0 : irgb[0])); } + if (rg) { *rg = (irgb[1] > 255 ? 255: (irgb[1] < 0 ? 0 : irgb[1])); } + if (rb) { *rb = (irgb[2] > 255 ? 255: (irgb[2] < 0 ? 0 : irgb[2])); } } class LightControllerClass { From c11952595efa2cd53c9e659242e373be4791aab9 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 16 May 2020 11:22:48 +0200 Subject: [PATCH 08/21] Change QPC detection from 4 to 7 power interrupts Change Quick Power Cycle detection from 4 to 7 power interrupts (#4066) --- RELEASENOTES.md | 3 +++ tasmota/CHANGELOG.md | 3 +++ tasmota/settings.ino | 10 +++++----- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5b5d1bebe..7c99a0a6d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -54,4 +54,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c ### Version 8.3.0.1 +- Change KNX pow function to approximative pow saving 5k of code space +- Change Mutichannel Gas sensor pow function to approximative pow saving 5k of code space +- Change Quick Power Cycle detection from 4 to 7 power interrupts (#4066) - Fix default state of ``SetOption73 0`` for button decoupling and send multi-press and hold MQTT messages diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 32a900f2c..7f48c3918 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -2,6 +2,9 @@ ### 8.3.0.1 20200514 +- Change KNX pow function to approximative pow saving 5k of code space +- Change Mutichannel Gas sensor pow function to approximative pow saving 5k of code space +- Change Quick Power Cycle detection from 4 to 7 power interrupts (#4066) - Fix default state of ``SetOption73 0`` for button decoupling and send multi-press and hold MQTT messages ## Released diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 2e7c04b7a..b189e1729 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -357,9 +357,9 @@ void UpdateQuickPowerCycle(bool update) #else // ESP32 QPCRead(&pc_register, sizeof(pc_register)); #endif // ESP8266 - ESP32 - if (update && ((pc_register & 0xFFFFFFF0) == 0xFFA55AB0)) { - uint32_t counter = ((pc_register & 0xF) << 1) & 0xF; - if (0 == counter) { // 4 power cycles in a row + if (update && ((pc_register & 0xFFFFFF80) == 0xFFA55A80)) { + uint32_t counter = ((pc_register & 0x7F) << 1) & 0x7F; + if (0 == counter) { // 7 power cycles in a row SettingsErase(3); // Quickly reset all settings including QuickPowerCycle flag EspRestart(); // And restart } else { @@ -372,8 +372,8 @@ void UpdateQuickPowerCycle(bool update) AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Flag %02X"), counter); } } - else if (pc_register != 0xFFA55ABF) { - pc_register = 0xFFA55ABF; + else if (pc_register != 0xFFA55AFF) { + pc_register = 0xFFA55AFF; #ifdef ESP8266 // Assume flash is default all ones and setting a bit to zero does not need an erase if (ESP.flashEraseSector(pc_location)) { From 79083a9882baa6cfc8f7034a88552fd4929025b5 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 16 May 2020 11:30:16 +0200 Subject: [PATCH 09/21] Update BUILDS.md --- BUILDS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BUILDS.md b/BUILDS.md index 7e56619f8..ec1720d44 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -75,6 +75,7 @@ | USE_DHT | - | - | x | x | x | x | x | | USE_MAX31855 | - | - | - | - | x | - | - | | USE_MAX31865 | - | - | - | - | - | - | - | +| USE_THERMOSTAT | - | - | - | - | - | - | - | | | | | | | | | | | Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks | USE_I2C | - | - | x | x | x | - | x | From 29177652dd18028bfc1b9fa7bdff04409afc8f29 Mon Sep 17 00:00:00 2001 From: nagyrobi Date: Sat, 16 May 2020 17:30:43 +0200 Subject: [PATCH 10/21] Add SetOption59 capability to MCP230XX --- tasmota/xsns_29_mcp230xx.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tasmota/xsns_29_mcp230xx.ino b/tasmota/xsns_29_mcp230xx.ino index 0a831d869..208fbcdd8 100644 --- a/tasmota/xsns_29_mcp230xx.ino +++ b/tasmota/xsns_29_mcp230xx.ino @@ -306,6 +306,9 @@ void MCP230xx_CheckForInterrupt(void) { ResponseTime_P(PSTR(",\"MCP230XX_INT\":{\"D%i\":%i,\"MS\":%lu}}"), intp+(mcp230xx_port*8), ((mcp230xx_intcap >> intp) & 0x01),millis_since_last_int); MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("MCP230XX_INT")); + if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/SENSOR in addition to stat/%topic%/RESULT + MqttPublishSensor(); + } } if (int_event) { char command[19]; // Theoretical max = 'event MCPINT_D16=1' so 18 + 1 (for the \n) From 80c8bf675c56e0a4ce81c43bbf1efa2fcfbd4a01 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 16 May 2020 17:44:29 +0200 Subject: [PATCH 11/21] Publish teleperiod data on command TelePeriod Publish teleperiod data on command ``TelePeriod`` (#2567) --- tasmota/support_command.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index cee21783e..6e0a74c93 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -1600,8 +1600,9 @@ void CmndTeleperiod(void) if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.tele_period = (1 == XdrvMailbox.payload) ? TELE_PERIOD : XdrvMailbox.payload; if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) Settings.tele_period = 10; // Do not allow periods < 10 seconds - tele_period = Settings.tele_period; +// tele_period = Settings.tele_period; } + tele_period = Settings.tele_period; // Show teleperiod data also on empty command ResponseCmndNumber(Settings.tele_period); } From 949248fa170298822c7f115e5e739b8e2da94c70 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 17 May 2020 10:37:42 +0200 Subject: [PATCH 12/21] Add unishox Python --- lib/Unishox-1.0-shadinger/python/unishox.py | 496 ++++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 lib/Unishox-1.0-shadinger/python/unishox.py diff --git a/lib/Unishox-1.0-shadinger/python/unishox.py b/lib/Unishox-1.0-shadinger/python/unishox.py new file mode 100644 index 000000000..bc8fc97e6 --- /dev/null +++ b/lib/Unishox-1.0-shadinger/python/unishox.py @@ -0,0 +1,496 @@ + + + + +cl_95 = [ 0x4000 + 3, 0x3F80 + 11, 0x3D80 + 11, 0x3C80 + 10, 0x3BE0 + 12, 0x3E80 + 10, 0x3F40 + 11, 0x3EC0 + 10, 0x3BA0 + 11, 0x3BC0 + 11, 0x3D60 + 11, 0x3B60 + 11, 0x3A80 + 10, 0x3AC0 + 10, 0x3A00 + 9, 0x3B00 + 10, 0x38C0 + 10, 0x3900 + 10, 0x3940 + 11, 0x3960 + 11, 0x3980 + 11, 0x39A0 + 11, 0x39C0 + 11, 0x39E0 + 12, 0x39F0 + 12, 0x3880 + 10, 0x3CC0 + 10, 0x3C00 + 9, 0x3D00 + 10, 0x3E00 + 9, 0x3F00 + 10, 0x3B40 + 11, 0x3BF0 + 12, 0x2B00 + 8, 0x21C0 + 11, 0x20C0 + 10, 0x2100 + 10, 0x2600 + 7, 0x2300 + 11, 0x21E0 + 12, 0x2140 + 11, 0x2D00 + 8, 0x2358 + 13, 0x2340 + 12, 0x2080 + 10, 0x21A0 + 11, 0x2E00 + 8, 0x2C00 + 8, 0x2180 + 11, 0x2350 + 13, 0x2F80 + 9, 0x2F00 + 9, 0x2A00 + 8, 0x2160 + 11, 0x2330 + 12, 0x21F0 + 12, 0x2360 + 13, 0x2320 + 12, 0x2368 + 13, 0x3DE0 + 12, 0x3FA0 + 11, 0x3DF0 + 12, 0x3D40 + 11, 0x3F60 + 11, 0x3FF0 + 12, 0xB000 + 4, 0x1C00 + 7, 0x0C00 + 6, 0x1000 + 6, 0x6000 + 3, 0x3000 + 7, 0x1E00 + 8, 0x1400 + 7, 0xD000 + 4, 0x3580 + 9, 0x3400 + 8, 0x0800 + 6, 0x1A00 + 7, 0xE000 + 4, 0xC000 + 4, 0x1800 + 7, 0x3500 + 9, 0xF800 + 5, 0xF000 + 5, 0xA000 + 4, 0x1600 + 7, 0x3300 + 8, 0x1F00 + 8, 0x3600 + 9, 0x3200 + 8, 0x3680 + 9, 0x3DA0 + 11, 0x3FC0 + 11, 0x3DC0 + 11, 0x3FE0 + 12 ] + + +# enum {SHX_STATE_1 = 1, SHX_STATE_2}; // removed Unicode state +SHX_STATE_1 = 1 +SHX_STATE_2 = 2 + + +SHX_SET1 = 0 +SHX_SET1A = 1 +SHX_SET1B = 2 +SHX_SET2 = 3 +SHX_SET3 = 4 +SHX_SET4 = 5 +SHX_SET4A = 6 + +# char sets[][11] PROGMEM = +# {{ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'}, +# { 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'}, +# {'f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0}, +# { 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'}, +# {'.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'}, +# {';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'}, +# {'=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`'}}; +sets = [[ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'], + [ 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'], + ['f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0], + [ 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'], + ['.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'], + [';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'], + ['=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`']] + + +us_vcode = [2 + (0 << 3), 3 + (3 << 3), 3 + (1 << 3), 4 + (6 << 3), 0, +# 5, 6, 7, 8, 9, 10 + 4 + (4 << 3), 3 + (2 << 3), 4 + (8 << 3), 0, 0, 0, +# 11, 12, 13, 14, 15 + 4 + (7 << 3), 0, 4 + (5 << 3), 0, 5 + (9 << 3), +# 16, 17, 18, 19, 20, 21, 22, 23 + 0, 0, 0, 0, 0, 0, 0, 0, +# 24, 25, 26, 27, 28, 29, 30, 31 + 0, 0, 0, 0, 0, 0, 0, 5 + (10 << 3) ] +# 0, 1, 2, 3, 4, 5, 6, 7, +us_hcode = [1 + (1 << 3), 2 + (0 << 3), 0, 3 + (2 << 3), 0, 0, 0, 5 + (3 << 3), +# 8, 9, 10, 11, 12, 13, 14, 15, + 0, 0, 0, 0, 0, 0, 0, 5 + (5 << 3), +# 16, 17, 18, 19, 20, 21, 22, 23 + 0, 0, 0, 0, 0, 0, 0, 5 + (4 << 3), +# 24, 25, 26, 27, 28, 29, 30, 31 + 0, 0, 0, 0, 0, 0, 0, 5 + (6 << 3) ] + +ESCAPE_MARKER = 0x2A + +TERM_CODE = 0x37C0 +TERM_CODE_LEN = 10 +DICT_CODE = 0x0000 +DICT_CODE_LEN = 5 +DICT_OTHER_CODE = 0x0000 +DICT_OTHER_CODE_LEN = 6 +RPT_CODE_TASMOTA = 0x3780 +RPT_CODE_TASMOTA_LEN = 10 +BACK2_STATE1_CODE = 0x2000 +BACK2_STATE1_CODE_LEN = 4 +BACK_FROM_UNI_CODE = 0xFE00 +BACK_FROM_UNI_CODE_LEN = 8 +LF_CODE = 0x3700 +LF_CODE_LEN = 9 +TAB_CODE = 0x2400 +TAB_CODE_LEN = 7 +ALL_UPPER_CODE = 0x2200 +ALL_UPPER_CODE_LEN = 8 +SW2_STATE2_CODE = 0x3800 +SW2_STATE2_CODE_LEN = 7 +ST2_SPC_CODE = 0x3B80 +ST2_SPC_CODE_LEN = 11 +BIN_CODE_TASMOTA = 0x8000 +BIN_CODE_TASMOTA_LEN = 3 + +NICE_LEN = 5 + +mask = [ 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF ] + +# Input +# out = bytearray +def append_bits(out, ol, code, clen, state): + #print("Append bits {ol} {code} {clen} {state}".format(ol=ol, code=code, clen=clen, state=state)) + if (state == SHX_STATE_2): + # remove change state prefix + if ((code >> 9) == 0x1C): + code <<= 7 + clen -= 7 + while (clen > 0): + cur_bit = ol % 8 + blen = 8 if (clen > 8) else clen + a_byte = (code >> 8) & mask[blen - 1] + #print("append_bits a_byte {ab} blen {blen}".format(ab=a_byte,blen=blen)) + a_byte >>= cur_bit + if (blen + cur_bit > 8): + blen = (8 - cur_bit) + if (cur_bit == 0): + out[ol // 8] = a_byte + else: + out[ol // 8] |= a_byte + code <<= blen + ol += blen + if (0 == ol % 8): + # we completed a full byte + last_c = out[(ol // 8) - 1] + if ((0 == last_c) or (ESCAPE_MARKER == last_c)): + out[ol // 8] = 1 + last_c # increment to 0x01 or 0x2B + out[(ol // 8) -1] = ESCAPE_MARKER # replace old value with marker + ol += 8 # add one full byte + clen -= blen; + return ol + + +codes = [ 0x82, 0xC3, 0xE5, 0xED, 0xF5 ] +bit_len = [ 5, 7, 9, 12, 16 ] + +def encodeCount(out, ol, count): + #print("encodeCount ol = {ol}, count = {count}".format(ol=ol, count=count)) + till = 0 + base = 0 + for i in range(len(bit_len)): + bit_len_i = bit_len[i] + till += (1 << bit_len_i) + if (count < till): + codes_i = codes[i] + ol = append_bits(out, ol, (codes_i & 0xF8) << 8, codes_i & 0x07, 1) + #print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(codes_i & 0xF8) << 8,len=codes_i & 0x07)) + ol = append_bits(out, ol, (count - base) << (16 - bit_len_i), bit_len_i, 1) + #print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(count - base) << (16 - bit_len_i),len=bit_len_i)) + return ol + base = till + return ol + +# Returns (int, ol, state, is_all_upper) +def matchOccurance(inn, lenn, l, out, ol, state, is_all_upper): + +#int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *state, byte *is_all_upper) { + # int j, k; + longest_dist = 0 + longest_len = 0 + #for (j = l - NICE_LEN; j >= 0; j--) { + j = l - NICE_LEN + while (j >= 0): + + k = l + #for (k = l; k < len && j + k - l < l; k++) { + while ((k < lenn) and (j + k - l < l)): + if (inn[k] != inn[j + k - l]): + break + k += 1 + if (k - l > NICE_LEN - 1): + match_len = k - l - NICE_LEN + match_dist = l - j - NICE_LEN + 1 + if (match_len > longest_len): + longest_len = match_len + longest_dist = match_dist + j -= 1 + + if (longest_len): + #print("longest_len {ll}".format(ll=longest_len)) + ol_save = ol + if (state == SHX_STATE_2 or is_all_upper): + is_all_upper = 0 + state = SHX_STATE_1 + ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state) + + ol = append_bits(out, ol, DICT_CODE, DICT_CODE_LEN, 1) + ol = encodeCount(out, ol, longest_len) + ol = encodeCount(out, ol, longest_dist) + #print("longest_len {ll} longest_dist {ld} ol {ols}-{ol}".format(ll=longest_len, ld=longest_dist, ol=ol, ols=ol_save)) + l += longest_len + NICE_LEN + l -= 1 + + return l, ol, state, is_all_upper + return -l, ol, state, is_all_upper + + +def unishox_compress(inn, len, out, len_out): +# int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) { + + ol = 0 + state = SHX_STATE_1 + is_all_upper = 0 + l = 0 + while (l < len): + # for (l=0; l 0): + #print("matchOccurance l = {l} l_old = {lo}".format(l=l,lo=l_old)) + l += 1 # for loop + continue + + l = -l + + if (state == SHX_STATE_2): # if Set2 + if ((c_in >= ord(' ') and c_in <= ord('@')) or (c_in >= ord('[') and c_in <= ord('`')) or (c_in >= ord('{') and c_in <= ord('~'))): + pass + else: + state = SHX_STATE_1 # back to Set1 and lower case + ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state) + + is_upper = 0 + if (c_in >= ord('A') and c_in <= ord('Z')): + is_upper = 1 + else: + if (is_all_upper): + is_all_upper = 0 + ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state) + + c_next = 0 + if (l+1 < len): + c_next = inn[l+1] + + if (c_in >= 32 and c_in <= 126): + if (is_upper and not is_all_upper): + ll=l+5 + # for (ll=l+5; ll>=l && ll=l and ll ord('Z')): + break + + ll -= 1 + + if (ll == l-1): + ol = append_bits(out, ol, ALL_UPPER_CODE, ALL_UPPER_CODE_LEN, state) # CapsLock + is_all_upper = 1; + + if (state == SHX_STATE_1 and c_in >= ord('0') and c_in <= ord('9')): + ol = append_bits(out, ol, SW2_STATE2_CODE, SW2_STATE2_CODE_LEN, state) # Switch to sticky Set2 + state = SHX_STATE_2 + + c_in -= 32 + if (is_all_upper and is_upper): + c_in += 32 + if (c_in == 0 and state == SHX_STATE_2): + ol = append_bits(out, ol, ST2_SPC_CODE, ST2_SPC_CODE_LEN, state) # space from Set2 ionstead of Set1 + else: + # ol = append_bits(out, ol, pgm_read_word(&c_95[c_in]), pgm_read_byte(&l_95[c_in]), state); // original version with c/l in split arrays + cl = cl_95[c_in] + ol = append_bits(out, ol, cl & 0xFFF0, cl & 0x000F, state) + + #lse: + # // if (c_in == 13 && c_next == 10) { // CRLF disabled + # // ol = append_bits(out, ol, CRLF_CODE, CRLF_CODE_LEN, state); // CRLF + # // l++; + # // } else + elif (c_in == 10): + ol = append_bits(out, ol, LF_CODE, LF_CODE_LEN, state) # LF + elif (c_in == '\t'): + ol = append_bits(out, ol, TAB_CODE, TAB_CODE_LEN, state) # TAB + else: + ol = append_bits(out, ol, BIN_CODE_TASMOTA, BIN_CODE_TASMOTA_LEN, state) # Binary, we reuse the Unicode marker which 3 bits instead of 9 + ol = encodeCount(out, ol, (255 - c_in) & 0xFF) + + + # check that we have some headroom in the output buffer + if (ol // 8 >= len_out - 4): + return -1 # we risk overflow and crash + + l += 1 + + bits = ol % 8 + if (bits): + ol = append_bits(out, ol, TERM_CODE, 8 - bits, 1) # 0011 0111 1100 0000 TERM = 0011 0111 11 + return (ol + 7) // 8 + # return ol // 8 + 1 if (ol%8) else 0 + + +def getBitVal(inn, bit_no, count): + c_in = inn[bit_no >> 3] + if ((bit_no >> 3) and (ESCAPE_MARKER == inn[(bit_no >> 3) - 1])): + c_in -= 1 + r = 1 << count if (c_in & (0x80 >> (bit_no % 8))) else 0 + #print("getBitVal r={r}".format(r=r)) + return r + +# Returns: +# 0..11 +# or -1 if end of stream +def getCodeIdx(code_type, inn, len, bit_no_p): + code = 0 + count = 0 + while (count < 5): + # detect marker + if (ESCAPE_MARKER == inn[bit_no_p >> 3]): + bit_no_p += 8 # skip marker + + if (bit_no_p >= len): + return -1, bit_no_p + + code += getBitVal(inn, bit_no_p, count) + bit_no_p += 1 + count += 1 + code_type_code = code_type[code] + if (code_type_code and (code_type_code & 0x07) == count): + #print("getCodeIdx = {r}".format(r=code_type_code >> 3)) + return code_type_code >> 3, bit_no_p + + #print("getCodeIdx not found = {r}".format(r=1)) + return 1, bit_no_p + +def getNumFromBits(inn, bit_no, count): + ret = 0 + while (count): + count -= 1 + if (ESCAPE_MARKER == inn[bit_no >> 3]): + bit_no += 8 # skip marker + ret += getBitVal(inn, bit_no, count) + bit_no += 1 + return ret + +def readCount(inn, bit_no_p, len): + (idx, bit_no_p) = getCodeIdx(us_hcode, inn, len, bit_no_p) + if (idx >= 1): idx -= 1; # we skip v = 1 (code '0') since we no more accept 2 bits encoding + if ((idx >= 5) or (idx < 0)): return 0, bit_no_p # unsupported or end of stream + till = 0 + bit_len_idx = 0 + base = 0 + #for (uint32_t i = 0; i <= idx; i++) { + i = 0 + while (i <= idx): + # for i in range(idx): + base = till + bit_len_idx = bit_len[i] + till += (1 << bit_len_idx) + i += 1 + + count = getNumFromBits(inn, bit_no_p, bit_len_idx) + base + #print("readCount getNumFromBits = {count} ({bl})".format(count=count,bl=bit_len_idx)) + + bit_no_p += bit_len_idx + return count, bit_no_p + + +def decodeRepeat(inn, len, out, ol, bit_no): + #print("decodeRepeat Enter") + (dict_len, bit_no) = readCount(inn, bit_no, len) + dict_len += NICE_LEN + (dist, bit_no) = readCount(inn, bit_no, len) + dist += NICE_LEN - 1 + #memcpy(out + ol, out + ol - dist, dict_len); + i = 0 + while (i < dict_len): + #for i in range(dict_len): + out[ol + i] = out[ol - dist + i] + i += 1 + ol += dict_len + + return ol, bit_no + +def unishox_decompress(inn, len, out, len_out): + ol = 0 + bit_no = 0 + dstate = SHX_SET1 + is_all_upper = 0 + + len <<= 3 # *8, len in bits + out[ol] = 0 + while (bit_no < len): + c = 0 + is_upper = is_all_upper + orig_bit_no = bit_no + (v, bit_no) = getCodeIdx(us_vcode, inn, len, bit_no) # read vCode + #print("bit_no {b}. v = {v}".format(b=bit_no,v=v)) + if (v < 0): break # end of stream + h = dstate # Set1 or Set2 + if (v == 0): # Switch which is common to Set1 and Set2, first entry + (h, bit_no) = getCodeIdx(us_hcode, inn, len, bit_no) # read hCode + #print("bit_no {b}. h = {h}".format(b=bit_no,h=h)) + if (h < 0): break # end of stream + if (h == SHX_SET1): # target is Set1 + if (dstate == SHX_SET1): # Switch from Set1 to Set1 us UpperCase + if (is_all_upper): # if CapsLock, then back to LowerCase + is_upper = 0 + is_all_upper = 0 + continue + + (v, bit_no) = getCodeIdx(us_vcode, inn, len, bit_no) # read again vCode + if (v < 0): break # end of stream + if (v == 0): + (h, bit_no) = getCodeIdx(us_hcode, inn, len, bit_no) # read second hCode + if (h < 0): break # end of stream + if (h == SHX_SET1): # If double Switch Set1, the CapsLock + is_all_upper = 1 + continue + + is_upper = 1 # anyways, still uppercase + else: + dstate = SHX_SET1 # if Set was not Set1, switch to Set1 + continue + + elif (h == SHX_SET2): # If Set2, switch dstate to Set2 + if (dstate == SHX_SET1): # TODO: is this test useful, there are only 2 states possible + dstate = SHX_SET2 + continue + + if (h != SHX_SET1): # all other Sets (why not else) + (v, bit_no) = getCodeIdx(us_vcode, inn, len, bit_no) # we changed set, now read vCode for char + if (v < 0): break # end of stream + + if (v == 0 and h == SHX_SET1A): + #print("v = 0, h = SHX_SET1A") + if (is_upper): + (temp, bit_no) = readCount(inn, bit_no, len) + out[ol] = 255 - temp # binary + ol += 1 + else: + (ol, bit_no) = decodeRepeat(inn, len, out, ol, bit_no) # dist + continue + + if (h == SHX_SET1 and v == 3): + # was Unicode, will do Binary instead + (temp, bit_no) = readCount(inn, bit_no, len) + out[ol] = 255 - temp # binary + ol += 1 + continue + + if (h < 7 and v < 11): # TODO: are these the actual limits? Not 11x7 ? + #print("h {h} v {v}".format(h=h,v=v)) + c = ord(sets[h][v]) # TODO + if (c >= ord('a') and c <= ord('z')): + if (is_upper): + c -= 32 # go to UpperCase for letters + else: # handle all other cases + if (is_upper and dstate == SHX_SET1 and v == 1): + c = ord('\t') # If UpperCase Space, change to TAB + if (h == SHX_SET1B): + if (8 == v): # was LF or RPT, now only LF + out[ol] = ord('\n') + ol += 1 + continue + + if (9 == v): # was CRLF, now RPT + (count, bit_no) = readCount(inn, bit_no, len) + count += 4 + if (ol + count >= len_out): + return -1 # overflow + + rpt_c = out[ol - 1] + while (count): + count -= 1 + out[ol] = rpt_c + ol += 1 + continue + + if (10 == v): + break # TERM, stop decoding + + out[ol] = c + ol += 1 + + if (ol >= len_out): + return -1 # overflow + + return ol + + + +if __name__ == "__main__": + inn = bytearray(b'ON Switch1#State==1 DO Add1 1 ENDON ON Var1#State==0 DO ShutterStop1 ENDON ON Var1#State==1 DO ShutterClose1 ENDON ON Var1#State>=2 DO Var1 0 ENDON ON Shutter1#Close DO Var1 0 ENDON ON Switch2#State==1 DO Add2 1 ENDON ON Var2#State==0 DO ShutterStop1 ENDON ON Var2#State==1 DO ShutterOpen1 ENDON ON Var2#State>=2 DO Var2 0 ENDON ON Shutter1#Open DO Var2 0 ENDON') + b = bytearray(2048) + l = unishox_compress(inn, len(inn), b, len(b)) + print("Compressed from {fromm} to {to} ({p}%)".format(fromm=len(inn),to=l,p=(100-l/len(inn)*100))) + + out = bytearray(2048) + l = unishox_decompress(b, l, out, len(out)) \ No newline at end of file From 1c441f9059cf49d842a11661eab245fce96619f2 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 17 May 2020 12:04:55 +0200 Subject: [PATCH 13/21] Free unused buffer --- tasmota/xnrg_02_cse7766.ino | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasmota/xnrg_02_cse7766.ino b/tasmota/xnrg_02_cse7766.ino index dae622845..f0aa210d1 100644 --- a/tasmota/xnrg_02_cse7766.ino +++ b/tasmota/xnrg_02_cse7766.ino @@ -243,10 +243,10 @@ void CseSnsInit(void) void CseDrvInit(void) { - Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE)); - if (Cse.rx_buffer != nullptr) { -// if (PinUsed(GPIO_CSE7766_RX) && PinUsed(GPIO_CSE7766_TX)) { - if (PinUsed(GPIO_CSE7766_RX)) { +// if (PinUsed(GPIO_CSE7766_RX) && PinUsed(GPIO_CSE7766_TX)) { + if (PinUsed(GPIO_CSE7766_RX)) { + Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE)); + if (Cse.rx_buffer != nullptr) { energy_flg = XNRG_02; } } From 5f7635e68ba30f08710472c5e1f0c104214549b6 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Sun, 17 May 2020 14:55:00 +0200 Subject: [PATCH 14/21] Enable user_config_override.h for ESP32 by default since we have always a user_config_override.h generated with override_copy.py if there is none. --- platformio_override_sample.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 5a66a3efb..071c28887 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -188,7 +188,7 @@ build_flags = ${esp_defaults.build_flags} -D sint16_t=int16_t -D memcpy_P=memcpy -D memcmp_P=memcmp -; -D USE_CONFIG_OVERRIDE + -D USE_CONFIG_OVERRIDE lib_extra_dirs = libesp32 From 0cfea4add6dcfd7218aa551a891bd25619fe207e Mon Sep 17 00:00:00 2001 From: Norbert Richter Date: Sun, 17 May 2020 16:28:09 +0200 Subject: [PATCH 15/21] Refactor unishox Python --- lib/Unishox-1.0-shadinger/python/unishox.py | 949 ++++++++++---------- 1 file changed, 483 insertions(+), 466 deletions(-) diff --git a/lib/Unishox-1.0-shadinger/python/unishox.py b/lib/Unishox-1.0-shadinger/python/unishox.py index bc8fc97e6..feafe0500 100644 --- a/lib/Unishox-1.0-shadinger/python/unishox.py +++ b/lib/Unishox-1.0-shadinger/python/unishox.py @@ -1,496 +1,513 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Python Class for compressing short strings. + +This class contains a highly modified and optimized version of Unishox +for Tasmota converted in C ported to Pyhton3. + +It was basically developed to individually compress and decompress small strings +(see https://github.com/siara-cc/Unishox) +In general compression utilities such as zip, gzip do not compress short strings +well and often expand them. They also use lots of memory which makes them unusable +in constrained environments like Arduino. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +class Unishox: + """ + This is a highly modified and optimized version of Unishox + for Tasmota, aimed at compressing `Rules` which are typically + short strings from 50 to 500 bytes. + + @author Stephan Hadinger + @revised Norbert Richter + """ + + # pylint: disable=bad-continuation,bad-whitespace,line-too-long + cl_95 = [0x4000 + 3, 0x3F80 + 11, 0x3D80 + 11, 0x3C80 + 10, 0x3BE0 + 12, 0x3E80 + 10, 0x3F40 + 11, 0x3EC0 + 10, 0x3BA0 + 11, 0x3BC0 + 11, 0x3D60 + 11, 0x3B60 + 11, 0x3A80 + 10, 0x3AC0 + 10, 0x3A00 + 9, 0x3B00 + 10, 0x38C0 + 10, 0x3900 + 10, 0x3940 + 11, 0x3960 + 11, 0x3980 + 11, 0x39A0 + 11, 0x39C0 + 11, 0x39E0 + 12, 0x39F0 + 12, 0x3880 + 10, 0x3CC0 + 10, 0x3C00 + 9, 0x3D00 + 10, 0x3E00 + 9, 0x3F00 + 10, 0x3B40 + 11, 0x3BF0 + 12, 0x2B00 + 8, 0x21C0 + 11, 0x20C0 + 10, 0x2100 + 10, 0x2600 + 7, 0x2300 + 11, 0x21E0 + 12, 0x2140 + 11, 0x2D00 + 8, 0x2358 + 13, 0x2340 + 12, 0x2080 + 10, 0x21A0 + 11, 0x2E00 + 8, 0x2C00 + 8, 0x2180 + 11, 0x2350 + 13, 0x2F80 + 9, 0x2F00 + 9, 0x2A00 + 8, 0x2160 + 11, 0x2330 + 12, 0x21F0 + 12, 0x2360 + 13, 0x2320 + 12, 0x2368 + 13, 0x3DE0 + 12, 0x3FA0 + 11, 0x3DF0 + 12, 0x3D40 + 11, 0x3F60 + 11, 0x3FF0 + 12, 0xB000 + 4, 0x1C00 + 7, 0x0C00 + 6, 0x1000 + 6, 0x6000 + 3, 0x3000 + 7, 0x1E00 + 8, 0x1400 + 7, 0xD000 + 4, 0x3580 + 9, 0x3400 + 8, 0x0800 + 6, 0x1A00 + 7, 0xE000 + 4, 0xC000 + 4, 0x1800 + 7, 0x3500 + 9, 0xF800 + 5, 0xF000 + 5, 0xA000 + 4, 0x1600 + 7, 0x3300 + 8, 0x1F00 + 8, 0x3600 + 9, 0x3200 + 8, 0x3680 + 9, 0x3DA0 + 11, 0x3FC0 + 11, 0x3DC0 + 11, 0x3FE0 + 12] + + # enum {SHX_STATE_1 = 1, SHX_STATE_2}; // removed Unicode state + SHX_STATE_1 = 1 + SHX_STATE_2 = 2 + + SHX_SET1 = 0 + SHX_SET1A = 1 + SHX_SET1B = 2 + SHX_SET2 = 3 + + sets = [[0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'], + [0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'], + ['f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0], + [0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'], + ['.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'], + [';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'], + ['=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`']] + + us_vcode = [2 + (0 << 3), 3 + (3 << 3), 3 + (1 << 3), 4 + (6 << 3), 0, + # 5, 6, 7, 8, 9, 10 + 4 + (4 << 3), 3 + (2 << 3), 4 + (8 << 3), 0, 0, 0, + # 11, 12, 13, 14, 15 + 4 + (7 << 3), 0, 4 + (5 << 3), 0, 5 + (9 << 3), + # 16, 17, 18, 19, 20, 21, 22, 23 + 0, 0, 0, 0, 0, 0, 0, 0, + # 24, 25, 26, 27, 28, 29, 30, 31 + 0, 0, 0, 0, 0, 0, 0, 5 + (10 << 3) ] + # 0, 1, 2, 3, 4, 5, 6, 7, + us_hcode = [1 + (1 << 3), 2 + (0 << 3), 0, 3 + (2 << 3), 0, 0, 0, 5 + (3 << 3), + # 8, 9, 10, 11, 12, 13, 14, 15, + 0, 0, 0, 0, 0, 0, 0, 5 + (5 << 3), + # 16, 17, 18, 19, 20, 21, 22, 23 + 0, 0, 0, 0, 0, 0, 0, 5 + (4 << 3), + # 24, 25, 26, 27, 28, 29, 30, 31 + 0, 0, 0, 0, 0, 0, 0, 5 + (6 << 3) ] + # pylint: enable=bad-continuation,bad-whitespace + + ESCAPE_MARKER = 0x2A + + TERM_CODE = 0x37C0 + # TERM_CODE_LEN = 10 + DICT_CODE = 0x0000 + DICT_CODE_LEN = 5 + #DICT_OTHER_CODE = 0x0000 + #DICT_OTHER_CODE_LEN = 6 + RPT_CODE_TASMOTA = 0x3780 + RPT_CODE_TASMOTA_LEN = 10 + BACK2_STATE1_CODE = 0x2000 + BACK2_STATE1_CODE_LEN = 4 + #BACK_FROM_UNI_CODE = 0xFE00 + #BACK_FROM_UNI_CODE_LEN = 8 + LF_CODE = 0x3700 + LF_CODE_LEN = 9 + TAB_CODE = 0x2400 + TAB_CODE_LEN = 7 + ALL_UPPER_CODE = 0x2200 + ALL_UPPER_CODE_LEN = 8 + SW2_STATE2_CODE = 0x3800 + SW2_STATE2_CODE_LEN = 7 + ST2_SPC_CODE = 0x3B80 + ST2_SPC_CODE_LEN = 11 + BIN_CODE_TASMOTA = 0x8000 + BIN_CODE_TASMOTA_LEN = 3 + + NICE_LEN = 5 + + mask = [0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF] + + # pylint: disable=missing-function-docstring,invalid-name + + # Input + # out = bytearray + def append_bits(self, out, ol, code, clen, state): + #print("Append bits {ol} {code} {clen} {state}".format(ol=ol, code=code, clen=clen, state=state)) + if state == self.SHX_STATE_2: + # remove change state prefix + if (code >> 9) == 0x1C: + code <<= 7 + clen -= 7 + while clen > 0: + cur_bit = ol % 8 + blen = 8 if (clen > 8) else clen + a_byte = (code >> 8) & self.mask[blen - 1] + #print("append_bits a_byte {ab} blen {blen}".format(ab=a_byte,blen=blen)) + a_byte >>= cur_bit + if blen + cur_bit > 8: + blen = (8 - cur_bit) + if cur_bit == 0: + out[ol // 8] = a_byte + else: + out[ol // 8] |= a_byte + code <<= blen + ol += blen + if 0 == ol % 8: # pylint: disable=misplaced-comparison-constant + # we completed a full byte + last_c = out[(ol // 8) - 1] + if last_c in (0, self.ESCAPE_MARKER): + out[ol // 8] = 1 + last_c # increment to 0x01 or 0x2B + out[(ol // 8) -1] = self.ESCAPE_MARKER # replace old value with marker + ol += 8 # add one full byte + clen -= blen + return ol + + codes = [0x82, 0xC3, 0xE5, 0xED, 0xF5] # pylint: disable=bad-whitespace + bit_len = [ 5, 7, 9, 12, 16] # pylint: disable=bad-whitespace + + def encodeCount(self, out, ol, count): + #print("encodeCount ol = {ol}, count = {count}".format(ol=ol, count=count)) + till = 0 + base = 0 + for i in range(len(self.bit_len)): + bit_len_i = self.bit_len[i] + till += (1 << bit_len_i) + if count < till: + codes_i = self.codes[i] + ol = self.append_bits(out, ol, (codes_i & 0xF8) << 8, codes_i & 0x07, 1) + #print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(codes_i & 0xF8) << 8,len=codes_i & 0x07)) + ol = self.append_bits(out, ol, (count - base) << (16 - bit_len_i), bit_len_i, 1) + #print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(count - base) << (16 - bit_len_i),len=bit_len_i)) + return ol + base = till + return ol + + # Returns (int, ol, state, is_all_upper) + def matchOccurance(self, inn, len_, l_, out, ol, state, is_all_upper): + # int j, k; + longest_dist = 0 + longest_len = 0 + #for (j = l_ - self.NICE_LEN; j >= 0; j--) { + j = l_ - self.NICE_LEN + while j >= 0: + k = l_ + #for (k = l_; k < len && j + k - l_ < l_; k++) { + while k < len_ and j + k - l_ < l_: + if inn[k] != inn[j + k - l_]: + break + k += 1 + if k - l_ > self.NICE_LEN - 1: + match_len = k - l_ - self.NICE_LEN + match_dist = l_ - j - self.NICE_LEN + 1 + if match_len > longest_len: + longest_len = match_len + longest_dist = match_dist + j -= 1 + + if longest_len: + #print("longest_len {ll}".format(ll=longest_len)) + #ol_save = ol + if state == self.SHX_STATE_2 or is_all_upper: + is_all_upper = 0 + state = self.SHX_STATE_1 + ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state) + + ol = self.append_bits(out, ol, self.DICT_CODE, self.DICT_CODE_LEN, 1) + ol = self.encodeCount(out, ol, longest_len) + ol = self.encodeCount(out, ol, longest_dist) + #print("longest_len {ll} longest_dist {ld} ol {ols}-{ol}".format(ll=longest_len, ld=longest_dist, ol=ol, ols=ol_save)) + l_ += longest_len + self.NICE_LEN + l_ -= 1 + + return l_, ol, state, is_all_upper + return -l_, ol, state, is_all_upper - - -cl_95 = [ 0x4000 + 3, 0x3F80 + 11, 0x3D80 + 11, 0x3C80 + 10, 0x3BE0 + 12, 0x3E80 + 10, 0x3F40 + 11, 0x3EC0 + 10, 0x3BA0 + 11, 0x3BC0 + 11, 0x3D60 + 11, 0x3B60 + 11, 0x3A80 + 10, 0x3AC0 + 10, 0x3A00 + 9, 0x3B00 + 10, 0x38C0 + 10, 0x3900 + 10, 0x3940 + 11, 0x3960 + 11, 0x3980 + 11, 0x39A0 + 11, 0x39C0 + 11, 0x39E0 + 12, 0x39F0 + 12, 0x3880 + 10, 0x3CC0 + 10, 0x3C00 + 9, 0x3D00 + 10, 0x3E00 + 9, 0x3F00 + 10, 0x3B40 + 11, 0x3BF0 + 12, 0x2B00 + 8, 0x21C0 + 11, 0x20C0 + 10, 0x2100 + 10, 0x2600 + 7, 0x2300 + 11, 0x21E0 + 12, 0x2140 + 11, 0x2D00 + 8, 0x2358 + 13, 0x2340 + 12, 0x2080 + 10, 0x21A0 + 11, 0x2E00 + 8, 0x2C00 + 8, 0x2180 + 11, 0x2350 + 13, 0x2F80 + 9, 0x2F00 + 9, 0x2A00 + 8, 0x2160 + 11, 0x2330 + 12, 0x21F0 + 12, 0x2360 + 13, 0x2320 + 12, 0x2368 + 13, 0x3DE0 + 12, 0x3FA0 + 11, 0x3DF0 + 12, 0x3D40 + 11, 0x3F60 + 11, 0x3FF0 + 12, 0xB000 + 4, 0x1C00 + 7, 0x0C00 + 6, 0x1000 + 6, 0x6000 + 3, 0x3000 + 7, 0x1E00 + 8, 0x1400 + 7, 0xD000 + 4, 0x3580 + 9, 0x3400 + 8, 0x0800 + 6, 0x1A00 + 7, 0xE000 + 4, 0xC000 + 4, 0x1800 + 7, 0x3500 + 9, 0xF800 + 5, 0xF000 + 5, 0xA000 + 4, 0x1600 + 7, 0x3300 + 8, 0x1F00 + 8, 0x3600 + 9, 0x3200 + 8, 0x3680 + 9, 0x3DA0 + 11, 0x3FC0 + 11, 0x3DC0 + 11, 0x3FE0 + 12 ] - - -# enum {SHX_STATE_1 = 1, SHX_STATE_2}; // removed Unicode state -SHX_STATE_1 = 1 -SHX_STATE_2 = 2 - - -SHX_SET1 = 0 -SHX_SET1A = 1 -SHX_SET1B = 2 -SHX_SET2 = 3 -SHX_SET3 = 4 -SHX_SET4 = 5 -SHX_SET4A = 6 - -# char sets[][11] PROGMEM = -# {{ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'}, -# { 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'}, -# {'f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0}, -# { 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'}, -# {'.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'}, -# {';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'}, -# {'=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`'}}; -sets = [[ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'], - [ 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'], - ['f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0], - [ 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'], - ['.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'], - [';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'], - ['=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`']] - - -us_vcode = [2 + (0 << 3), 3 + (3 << 3), 3 + (1 << 3), 4 + (6 << 3), 0, -# 5, 6, 7, 8, 9, 10 - 4 + (4 << 3), 3 + (2 << 3), 4 + (8 << 3), 0, 0, 0, -# 11, 12, 13, 14, 15 - 4 + (7 << 3), 0, 4 + (5 << 3), 0, 5 + (9 << 3), -# 16, 17, 18, 19, 20, 21, 22, 23 - 0, 0, 0, 0, 0, 0, 0, 0, -# 24, 25, 26, 27, 28, 29, 30, 31 - 0, 0, 0, 0, 0, 0, 0, 5 + (10 << 3) ] -# 0, 1, 2, 3, 4, 5, 6, 7, -us_hcode = [1 + (1 << 3), 2 + (0 << 3), 0, 3 + (2 << 3), 0, 0, 0, 5 + (3 << 3), -# 8, 9, 10, 11, 12, 13, 14, 15, - 0, 0, 0, 0, 0, 0, 0, 5 + (5 << 3), -# 16, 17, 18, 19, 20, 21, 22, 23 - 0, 0, 0, 0, 0, 0, 0, 5 + (4 << 3), -# 24, 25, 26, 27, 28, 29, 30, 31 - 0, 0, 0, 0, 0, 0, 0, 5 + (6 << 3) ] - -ESCAPE_MARKER = 0x2A - -TERM_CODE = 0x37C0 -TERM_CODE_LEN = 10 -DICT_CODE = 0x0000 -DICT_CODE_LEN = 5 -DICT_OTHER_CODE = 0x0000 -DICT_OTHER_CODE_LEN = 6 -RPT_CODE_TASMOTA = 0x3780 -RPT_CODE_TASMOTA_LEN = 10 -BACK2_STATE1_CODE = 0x2000 -BACK2_STATE1_CODE_LEN = 4 -BACK_FROM_UNI_CODE = 0xFE00 -BACK_FROM_UNI_CODE_LEN = 8 -LF_CODE = 0x3700 -LF_CODE_LEN = 9 -TAB_CODE = 0x2400 -TAB_CODE_LEN = 7 -ALL_UPPER_CODE = 0x2200 -ALL_UPPER_CODE_LEN = 8 -SW2_STATE2_CODE = 0x3800 -SW2_STATE2_CODE_LEN = 7 -ST2_SPC_CODE = 0x3B80 -ST2_SPC_CODE_LEN = 11 -BIN_CODE_TASMOTA = 0x8000 -BIN_CODE_TASMOTA_LEN = 3 - -NICE_LEN = 5 - -mask = [ 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF ] - -# Input -# out = bytearray -def append_bits(out, ol, code, clen, state): - #print("Append bits {ol} {code} {clen} {state}".format(ol=ol, code=code, clen=clen, state=state)) - if (state == SHX_STATE_2): - # remove change state prefix - if ((code >> 9) == 0x1C): - code <<= 7 - clen -= 7 - while (clen > 0): - cur_bit = ol % 8 - blen = 8 if (clen > 8) else clen - a_byte = (code >> 8) & mask[blen - 1] - #print("append_bits a_byte {ab} blen {blen}".format(ab=a_byte,blen=blen)) - a_byte >>= cur_bit - if (blen + cur_bit > 8): - blen = (8 - cur_bit) - if (cur_bit == 0): - out[ol // 8] = a_byte - else: - out[ol // 8] |= a_byte - code <<= blen - ol += blen - if (0 == ol % 8): - # we completed a full byte - last_c = out[(ol // 8) - 1] - if ((0 == last_c) or (ESCAPE_MARKER == last_c)): - out[ol // 8] = 1 + last_c # increment to 0x01 or 0x2B - out[(ol // 8) -1] = ESCAPE_MARKER # replace old value with marker - ol += 8 # add one full byte - clen -= blen; - return ol - - -codes = [ 0x82, 0xC3, 0xE5, 0xED, 0xF5 ] -bit_len = [ 5, 7, 9, 12, 16 ] - -def encodeCount(out, ol, count): - #print("encodeCount ol = {ol}, count = {count}".format(ol=ol, count=count)) - till = 0 - base = 0 - for i in range(len(bit_len)): - bit_len_i = bit_len[i] - till += (1 << bit_len_i) - if (count < till): - codes_i = codes[i] - ol = append_bits(out, ol, (codes_i & 0xF8) << 8, codes_i & 0x07, 1) - #print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(codes_i & 0xF8) << 8,len=codes_i & 0x07)) - ol = append_bits(out, ol, (count - base) << (16 - bit_len_i), bit_len_i, 1) - #print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(count - base) << (16 - bit_len_i),len=bit_len_i)) - return ol - base = till - return ol - -# Returns (int, ol, state, is_all_upper) -def matchOccurance(inn, lenn, l, out, ol, state, is_all_upper): - -#int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *state, byte *is_all_upper) { - # int j, k; - longest_dist = 0 - longest_len = 0 - #for (j = l - NICE_LEN; j >= 0; j--) { - j = l - NICE_LEN - while (j >= 0): - - k = l - #for (k = l; k < len && j + k - l < l; k++) { - while ((k < lenn) and (j + k - l < l)): - if (inn[k] != inn[j + k - l]): - break - k += 1 - if (k - l > NICE_LEN - 1): - match_len = k - l - NICE_LEN - match_dist = l - j - NICE_LEN + 1 - if (match_len > longest_len): - longest_len = match_len - longest_dist = match_dist - j -= 1 - - if (longest_len): - #print("longest_len {ll}".format(ll=longest_len)) - ol_save = ol - if (state == SHX_STATE_2 or is_all_upper): - is_all_upper = 0 - state = SHX_STATE_1 - ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state) - - ol = append_bits(out, ol, DICT_CODE, DICT_CODE_LEN, 1) - ol = encodeCount(out, ol, longest_len) - ol = encodeCount(out, ol, longest_dist) - #print("longest_len {ll} longest_dist {ld} ol {ols}-{ol}".format(ll=longest_len, ld=longest_dist, ol=ol, ols=ol_save)) - l += longest_len + NICE_LEN - l -= 1 - - return l, ol, state, is_all_upper - return -l, ol, state, is_all_upper - - -def unishox_compress(inn, len, out, len_out): -# int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) { - - ol = 0 - state = SHX_STATE_1 - is_all_upper = 0 - l = 0 - while (l < len): - # for (l=0; l 0): - #print("matchOccurance l = {l} l_old = {lo}".format(l=l,lo=l_old)) - l += 1 # for loop - continue - - l = -l - - if (state == SHX_STATE_2): # if Set2 - if ((c_in >= ord(' ') and c_in <= ord('@')) or (c_in >= ord('[') and c_in <= ord('`')) or (c_in >= ord('{') and c_in <= ord('~'))): - pass - else: - state = SHX_STATE_1 # back to Set1 and lower case - ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state) - - is_upper = 0 - if (c_in >= ord('A') and c_in <= ord('Z')): - is_upper = 1 - else: - if (is_all_upper): + def compress(self, inn, len_, out, len_out): + ol = 0 + state = self.SHX_STATE_1 is_all_upper = 0 - ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state) + l = 0 + while l < len_: + # for (l=0; l= 32 and c_in <= 126): - if (is_upper and not is_all_upper): - ll=l+5 - # for (ll=l+5; ll>=l && ll=l and ll ord('Z')): - break + if l and l < len_ - 4: + if c_in == inn[l - 1] and c_in == inn[l + 1] and c_in == inn[l + 2] and c_in == inn[l + 3]: + rpt_count = l + 4 + while rpt_count < len_ and inn[rpt_count] == c_in: + rpt_count += 1 + rpt_count -= l - ll -= 1 + if state == self.SHX_STATE_2 or is_all_upper: + is_all_upper = 0 + state = self.SHX_STATE_1 + ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state) # back to lower case and Set1 - if (ll == l-1): - ol = append_bits(out, ol, ALL_UPPER_CODE, ALL_UPPER_CODE_LEN, state) # CapsLock - is_all_upper = 1; + ol = self.append_bits(out, ol, self.RPT_CODE_TASMOTA, self.RPT_CODE_TASMOTA_LEN, 1) # reusing CRLF for RPT + ol = self.encodeCount(out, ol, rpt_count - 4) + l += rpt_count + #l -= 1 + continue - if (state == SHX_STATE_1 and c_in >= ord('0') and c_in <= ord('9')): - ol = append_bits(out, ol, SW2_STATE2_CODE, SW2_STATE2_CODE_LEN, state) # Switch to sticky Set2 - state = SHX_STATE_2 + if l < (len_ - self.NICE_LEN + 1): + #l_old = l + (l, ol, state, is_all_upper) = self.matchOccurance(inn, len_, l, out, ol, state, is_all_upper) + if l > 0: + #print("matchOccurance l = {l} l_old = {lo}".format(l=l,lo=l_old)) + l += 1 # for loop + continue - c_in -= 32 - if (is_all_upper and is_upper): - c_in += 32 - if (c_in == 0 and state == SHX_STATE_2): - ol = append_bits(out, ol, ST2_SPC_CODE, ST2_SPC_CODE_LEN, state) # space from Set2 ionstead of Set1 - else: - # ol = append_bits(out, ol, pgm_read_word(&c_95[c_in]), pgm_read_byte(&l_95[c_in]), state); // original version with c/l in split arrays - cl = cl_95[c_in] - ol = append_bits(out, ol, cl & 0xFFF0, cl & 0x000F, state) + l = -l - #lse: - # // if (c_in == 13 && c_next == 10) { // CRLF disabled - # // ol = append_bits(out, ol, CRLF_CODE, CRLF_CODE_LEN, state); // CRLF - # // l++; - # // } else - elif (c_in == 10): - ol = append_bits(out, ol, LF_CODE, LF_CODE_LEN, state) # LF - elif (c_in == '\t'): - ol = append_bits(out, ol, TAB_CODE, TAB_CODE_LEN, state) # TAB - else: - ol = append_bits(out, ol, BIN_CODE_TASMOTA, BIN_CODE_TASMOTA_LEN, state) # Binary, we reuse the Unicode marker which 3 bits instead of 9 - ol = encodeCount(out, ol, (255 - c_in) & 0xFF) - + if state == self.SHX_STATE_2: # if Set2 + if ord(' ') <= c_in <= ord('@') or ord('[') <= c_in <= ord('`') or ord('{') <= c_in <= ord('~'): + pass + else: + state = self.SHX_STATE_1 # back to Set1 and lower case + ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state) - # check that we have some headroom in the output buffer - if (ol // 8 >= len_out - 4): - return -1 # we risk overflow and crash - - l += 1 - - bits = ol % 8 - if (bits): - ol = append_bits(out, ol, TERM_CODE, 8 - bits, 1) # 0011 0111 1100 0000 TERM = 0011 0111 11 - return (ol + 7) // 8 - # return ol // 8 + 1 if (ol%8) else 0 - - -def getBitVal(inn, bit_no, count): - c_in = inn[bit_no >> 3] - if ((bit_no >> 3) and (ESCAPE_MARKER == inn[(bit_no >> 3) - 1])): - c_in -= 1 - r = 1 << count if (c_in & (0x80 >> (bit_no % 8))) else 0 - #print("getBitVal r={r}".format(r=r)) - return r - -# Returns: -# 0..11 -# or -1 if end of stream -def getCodeIdx(code_type, inn, len, bit_no_p): - code = 0 - count = 0 - while (count < 5): - # detect marker - if (ESCAPE_MARKER == inn[bit_no_p >> 3]): - bit_no_p += 8 # skip marker - - if (bit_no_p >= len): - return -1, bit_no_p - - code += getBitVal(inn, bit_no_p, count) - bit_no_p += 1 - count += 1 - code_type_code = code_type[code] - if (code_type_code and (code_type_code & 0x07) == count): - #print("getCodeIdx = {r}".format(r=code_type_code >> 3)) - return code_type_code >> 3, bit_no_p - - #print("getCodeIdx not found = {r}".format(r=1)) - return 1, bit_no_p - -def getNumFromBits(inn, bit_no, count): - ret = 0 - while (count): - count -= 1 - if (ESCAPE_MARKER == inn[bit_no >> 3]): - bit_no += 8 # skip marker - ret += getBitVal(inn, bit_no, count) - bit_no += 1 - return ret - -def readCount(inn, bit_no_p, len): - (idx, bit_no_p) = getCodeIdx(us_hcode, inn, len, bit_no_p) - if (idx >= 1): idx -= 1; # we skip v = 1 (code '0') since we no more accept 2 bits encoding - if ((idx >= 5) or (idx < 0)): return 0, bit_no_p # unsupported or end of stream - till = 0 - bit_len_idx = 0 - base = 0 - #for (uint32_t i = 0; i <= idx; i++) { - i = 0 - while (i <= idx): - # for i in range(idx): - base = till - bit_len_idx = bit_len[i] - till += (1 << bit_len_idx) - i += 1 - - count = getNumFromBits(inn, bit_no_p, bit_len_idx) + base - #print("readCount getNumFromBits = {count} ({bl})".format(count=count,bl=bit_len_idx)) - - bit_no_p += bit_len_idx - return count, bit_no_p - - -def decodeRepeat(inn, len, out, ol, bit_no): - #print("decodeRepeat Enter") - (dict_len, bit_no) = readCount(inn, bit_no, len) - dict_len += NICE_LEN - (dist, bit_no) = readCount(inn, bit_no, len) - dist += NICE_LEN - 1 - #memcpy(out + ol, out + ol - dist, dict_len); - i = 0 - while (i < dict_len): - #for i in range(dict_len): - out[ol + i] = out[ol - dist + i] - i += 1 - ol += dict_len - - return ol, bit_no - -def unishox_decompress(inn, len, out, len_out): - ol = 0 - bit_no = 0 - dstate = SHX_SET1 - is_all_upper = 0 - - len <<= 3 # *8, len in bits - out[ol] = 0 - while (bit_no < len): - c = 0 - is_upper = is_all_upper - orig_bit_no = bit_no - (v, bit_no) = getCodeIdx(us_vcode, inn, len, bit_no) # read vCode - #print("bit_no {b}. v = {v}".format(b=bit_no,v=v)) - if (v < 0): break # end of stream - h = dstate # Set1 or Set2 - if (v == 0): # Switch which is common to Set1 and Set2, first entry - (h, bit_no) = getCodeIdx(us_hcode, inn, len, bit_no) # read hCode - #print("bit_no {b}. h = {h}".format(b=bit_no,h=h)) - if (h < 0): break # end of stream - if (h == SHX_SET1): # target is Set1 - if (dstate == SHX_SET1): # Switch from Set1 to Set1 us UpperCase - if (is_all_upper): # if CapsLock, then back to LowerCase is_upper = 0 - is_all_upper = 0 - continue + if ord('A') <= c_in <= ord('Z'): + is_upper = 1 + else: + if is_all_upper: + is_all_upper = 0 + ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state) - (v, bit_no) = getCodeIdx(us_vcode, inn, len, bit_no) # read again vCode - if (v < 0): break # end of stream - if (v == 0): - (h, bit_no) = getCodeIdx(us_hcode, inn, len, bit_no) # read second hCode - if (h < 0): break # end of stream - if (h == SHX_SET1): # If double Switch Set1, the CapsLock - is_all_upper = 1 - continue + if 32 <= c_in <= 126: + if is_upper and not is_all_upper: + ll = l+5 + # for (ll=l+5; ll>=l && ll ord('Z'): + break - is_upper = 1 # anyways, still uppercase - else: - dstate = SHX_SET1 # if Set was not Set1, switch to Set1 - continue + ll -= 1 - elif (h == SHX_SET2): # If Set2, switch dstate to Set2 - if (dstate == SHX_SET1): # TODO: is this test useful, there are only 2 states possible - dstate = SHX_SET2 - continue + if ll == l-1: + ol = self.append_bits(out, ol, self.ALL_UPPER_CODE, self.ALL_UPPER_CODE_LEN, state) # CapsLock + is_all_upper = 1 - if (h != SHX_SET1): # all other Sets (why not else) - (v, bit_no) = getCodeIdx(us_vcode, inn, len, bit_no) # we changed set, now read vCode for char - if (v < 0): break # end of stream + if state == self.SHX_STATE_1 and ord('0') <= c_in <= ord('9'): + ol = self.append_bits(out, ol, self.SW2_STATE2_CODE, self.SW2_STATE2_CODE_LEN, state) # Switch to sticky Set2 + state = self.SHX_STATE_2 - if (v == 0 and h == SHX_SET1A): - #print("v = 0, h = SHX_SET1A") - if (is_upper): - (temp, bit_no) = readCount(inn, bit_no, len) - out[ol] = 255 - temp # binary - ol += 1 - else: - (ol, bit_no) = decodeRepeat(inn, len, out, ol, bit_no) # dist - continue + c_in -= 32 + if is_all_upper and is_upper: + c_in += 32 + if c_in == 0 and state == self.SHX_STATE_2: + ol = self.append_bits(out, ol, self.ST2_SPC_CODE, self.ST2_SPC_CODE_LEN, state) # space from Set2 ionstead of Set1 + else: + # ol = self.append_bits(out, ol, pgm_read_word(&c_95[c_in]), pgm_read_byte(&l_95[c_in]), state); // original version with c/l in split arrays + cl = self.cl_95[c_in] + ol = self.append_bits(out, ol, cl & 0xFFF0, cl & 0x000F, state) - if (h == SHX_SET1 and v == 3): - # was Unicode, will do Binary instead - (temp, bit_no) = readCount(inn, bit_no, len) - out[ol] = 255 - temp # binary - ol += 1 - continue + elif c_in == 10: + ol = self.append_bits(out, ol, self.LF_CODE, self.LF_CODE_LEN, state) # LF + elif c_in == '\t': + ol = self.append_bits(out, ol, self.TAB_CODE, self.TAB_CODE_LEN, state) # TAB + else: + ol = self.append_bits(out, ol, self.BIN_CODE_TASMOTA, self.BIN_CODE_TASMOTA_LEN, state) # Binary, we reuse the Unicode marker which 3 bits instead of 9 + ol = self.encodeCount(out, ol, (255 - c_in) & 0xFF) - if (h < 7 and v < 11): # TODO: are these the actual limits? Not 11x7 ? - #print("h {h} v {v}".format(h=h,v=v)) - c = ord(sets[h][v]) # TODO - if (c >= ord('a') and c <= ord('z')): - if (is_upper): - c -= 32 # go to UpperCase for letters - else: # handle all other cases - if (is_upper and dstate == SHX_SET1 and v == 1): - c = ord('\t') # If UpperCase Space, change to TAB - if (h == SHX_SET1B): - if (8 == v): # was LF or RPT, now only LF - out[ol] = ord('\n') - ol += 1 - continue - if (9 == v): # was CRLF, now RPT - (count, bit_no) = readCount(inn, bit_no, len) - count += 4 - if (ol + count >= len_out): - return -1 # overflow + # check that we have some headroom in the output buffer + if ol // 8 >= len_out - 4: + return -1 # we risk overflow and crash - rpt_c = out[ol - 1] - while (count): + l += 1 + + bits = ol % 8 + if bits: + ol = self.append_bits(out, ol, self.TERM_CODE, 8 - bits, 1) # 0011 0111 1100 0000 TERM = 0011 0111 11 + return (ol + 7) // 8 + # return ol // 8 + 1 if (ol%8) else 0 + + + def getBitVal(self, inn, bit_no, count): + c_in = inn[bit_no >> 3] + if bit_no >> 3 and self.ESCAPE_MARKER == inn[(bit_no >> 3) - 1]: + c_in -= 1 + r = 1 << count if (c_in & (0x80 >> (bit_no % 8))) else 0 + #print("getBitVal r={r}".format(r=r)) + return r + + # Returns: + # 0..11 + # or -1 if end of stream + def getCodeIdx(self, code_type, inn, len_, bit_no_p): + code = 0 + count = 0 + while count < 5: + # detect marker + if self.ESCAPE_MARKER == inn[bit_no_p >> 3]: + bit_no_p += 8 # skip marker + + if bit_no_p >= len_: + return -1, bit_no_p + + code += self.getBitVal(inn, bit_no_p, count) + bit_no_p += 1 + count += 1 + code_type_code = code_type[code] + if code_type_code and (code_type_code & 0x07) == count: + #print("getCodeIdx = {r}".format(r=code_type_code >> 3)) + return code_type_code >> 3, bit_no_p + + #print("getCodeIdx not found = {r}".format(r=1)) + return 1, bit_no_p + + def getNumFromBits(self, inn, bit_no, count): + ret = 0 + while count: count -= 1 - out[ol] = rpt_c + if self.ESCAPE_MARKER == inn[bit_no >> 3]: + bit_no += 8 # skip marker + ret += self.getBitVal(inn, bit_no, count) + bit_no += 1 + return ret + + def readCount(self, inn, bit_no_p, len_): + (idx, bit_no_p) = self.getCodeIdx(self.us_hcode, inn, len_, bit_no_p) + if idx >= 1: + idx -= 1 # we skip v = 1 (code '0') since we no more accept 2 bits encoding + if idx >= 5 or idx < 0: + return 0, bit_no_p # unsupported or end of stream + till = 0 + bit_len_idx = 0 + base = 0 + #for (uint32_t i = 0; i <= idx; i++) { + i = 0 + while i <= idx: + # for i in range(idx): + base = till + bit_len_idx = self.bit_len[i] + till += (1 << bit_len_idx) + i += 1 + + count = self.getNumFromBits(inn, bit_no_p, bit_len_idx) + base + #print("readCount getNumFromBits = {count} ({bl})".format(count=count,bl=bit_len_idx)) + + bit_no_p += bit_len_idx + return count, bit_no_p + + def decodeRepeat(self, inn, len_, out, ol, bit_no): + #print("decodeRepeat Enter") + (dict_len, bit_no) = self.readCount(inn, bit_no, len_) + dict_len += self.NICE_LEN + (dist, bit_no) = self.readCount(inn, bit_no, len_) + dist += self.NICE_LEN - 1 + #memcpy(out + ol, out + ol - dist, dict_len); + i = 0 + while i < dict_len: + #for i in range(dict_len): + out[ol + i] = out[ol - dist + i] + i += 1 + ol += dict_len + + return ol, bit_no + + def decompress(self, inn, len_, out, len_out): + ol = 0 + bit_no = 0 + dstate = self.SHX_SET1 + is_all_upper = 0 + + len_ <<= 3 # *8, len_ in bits + out[ol] = 0 + while bit_no < len_: + c = 0 + is_upper = is_all_upper + (v, bit_no) = self.getCodeIdx(self.us_vcode, inn, len_, bit_no) # read vCode + #print("bit_no {b}. v = {v}".format(b=bit_no,v=v)) + if v < 0: + break # end of stream + h = dstate # Set1 or Set2 + if v == 0: # Switch which is common to Set1 and Set2, first entry + (h, bit_no) = self.getCodeIdx(self.us_hcode, inn, len_, bit_no) # read hCode + #print("bit_no {b}. h = {h}".format(b=bit_no,h=h)) + if h < 0: + break # end of stream + if h == self.SHX_SET1: # target is Set1 + if dstate == self.SHX_SET1: # Switch from Set1 to Set1 us UpperCase + if is_all_upper: # if CapsLock, then back to LowerCase + is_upper = 0 + is_all_upper = 0 + continue + + (v, bit_no) = self.getCodeIdx(self.us_vcode, inn, len_, bit_no) # read again vCode + if v < 0: + break # end of stream + if v == 0: + (h, bit_no) = self.getCodeIdx(self.us_hcode, inn, len_, bit_no) # read second hCode + if h < 0: + break # end of stream + if h == self.SHX_SET1: # If double Switch Set1, the CapsLock + is_all_upper = 1 + continue + + is_upper = 1 # anyways, still uppercase + else: + dstate = self.SHX_SET1 # if Set was not Set1, switch to Set1 + continue + + elif h == self.SHX_SET2: # If Set2, switch dstate to Set2 + if dstate == self.SHX_SET1: + dstate = self.SHX_SET2 + continue + + if h != self.SHX_SET1: # all other Sets (why not else) + (v, bit_no) = self.getCodeIdx(self.us_vcode, inn, len_, bit_no) # we changed set, now read vCode for char + if v < 0: + break # end of stream + + if v == 0 and h == self.SHX_SET1A: + #print("v = 0, h = self.SHX_SET1A") + if is_upper: + (temp, bit_no) = self.readCount(inn, bit_no, len_) + out[ol] = 255 - temp # binary + ol += 1 + else: + (ol, bit_no) = self.decodeRepeat(inn, len_, out, ol, bit_no) # dist + continue + + if h == self.SHX_SET1 and v == 3: + # was Unicode, will do Binary instead + (temp, bit_no) = self.readCount(inn, bit_no, len_) + out[ol] = 255 - temp # binary + ol += 1 + continue + + if h < 7 and v < 11: + #print("h {h} v {v}".format(h=h,v=v)) + c = ord(self.sets[h][v]) + if ord('a') <= c <= ord('z'): + if is_upper: + c -= 32 # go to UpperCase for letters + else: # handle all other cases + if is_upper and dstate == self.SHX_SET1 and v == 1: + c = ord('\t') # If UpperCase Space, change to TAB + if h == self.SHX_SET1B: + if 8 == v: # was LF or RPT, now only LF # pylint: disable=misplaced-comparison-constant + out[ol] = ord('\n') + ol += 1 + continue + + if 9 == v: # was CRLF, now RPT # pylint: disable=misplaced-comparison-constant + (count, bit_no) = self.readCount(inn, bit_no, len_) + count += 4 + if ol + count >= len_out: + return -1 # overflow + + rpt_c = out[ol - 1] + while count: + count -= 1 + out[ol] = rpt_c + ol += 1 + continue + + if 10 == v: # pylint: disable=misplaced-comparison-constant + break # TERM, stop decoding + + out[ol] = c ol += 1 - continue - if (10 == v): - break # TERM, stop decoding + if ol >= len_out: + return -1 # overflow - out[ol] = c - ol += 1 - - if (ol >= len_out): - return -1 # overflow - - return ol + return ol + # pylint: enable=missing-function-docstring if __name__ == "__main__": - inn = bytearray(b'ON Switch1#State==1 DO Add1 1 ENDON ON Var1#State==0 DO ShutterStop1 ENDON ON Var1#State==1 DO ShutterClose1 ENDON ON Var1#State>=2 DO Var1 0 ENDON ON Shutter1#Close DO Var1 0 ENDON ON Switch2#State==1 DO Add2 1 ENDON ON Var2#State==0 DO ShutterStop1 ENDON ON Var2#State==1 DO ShutterOpen1 ENDON ON Var2#State>=2 DO Var2 0 ENDON ON Shutter1#Open DO Var2 0 ENDON') - b = bytearray(2048) - l = unishox_compress(inn, len(inn), b, len(b)) - print("Compressed from {fromm} to {to} ({p}%)".format(fromm=len(inn),to=l,p=(100-l/len(inn)*100))) + # pylint: disable=line-too-long + UNISHOX = Unishox() + BYTES_ = bytearray(2048) + INN = bytearray(b'ON Switch1#State==1 DO Add1 1 ENDON ON Var1#State==0 DO ShutterStop1 ENDON ON Var1#State==1 DO ShutterClose1 ENDON ON Var1#State>=2 DO Var1 0 ENDON ON Shutter1#Close DO Var1 0 ENDON ON Switch2#State==1 DO Add2 1 ENDON ON Var2#State==0 DO ShutterStop1 ENDON ON Var2#State==1 DO ShutterOpen1 ENDON ON Var2#State>=2 DO Var2 0 ENDON ON Shutter1#Open DO Var2 0 ENDON') + LEN_ = UNISHOX.compress(INN, len(INN), BYTES_, len(BYTES_)) + print("Compressed from {fromm} to {to} ({p}%)".format(fromm=len(INN), to=LEN_, p=(100-LEN_/len(INN)*100))) - out = bytearray(2048) - l = unishox_decompress(b, l, out, len(out)) \ No newline at end of file + OUT = bytearray(2048) + LEN_ = UNISHOX.decompress(BYTES_, LEN_, OUT, len(OUT)) + print(str(OUT, 'utf-8').split('\x00')[0]) From d7f6c78ed953f3d9fb7dbbe6a21407de32d85d1e Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 17 May 2020 17:10:17 +0200 Subject: [PATCH 16/21] Add command ``DeviceName`` Add command ``DeviceName`` defaults to FriendlyName1 and replaces FriendlyName1 in GUI --- RELEASENOTES.md | 3 ++- tasmota/CHANGELOG.md | 4 ++++ tasmota/i18n.h | 1 + tasmota/language/bg_BG.h | 1 + tasmota/language/cs_CZ.h | 1 + tasmota/language/de_DE.h | 1 + tasmota/language/el_GR.h | 1 + tasmota/language/en_GB.h | 1 + tasmota/language/es_ES.h | 1 + tasmota/language/fr_FR.h | 1 + tasmota/language/he_HE.h | 1 + tasmota/language/hu_HU.h | 1 + tasmota/language/it_IT.h | 1 + tasmota/language/ko_KO.h | 1 + tasmota/language/nl_NL.h | 1 + tasmota/language/pl_PL.h | 1 + tasmota/language/pt_BR.h | 1 + tasmota/language/pt_PT.h | 1 + tasmota/language/ro_RO.h | 1 + tasmota/language/ru_RU.h | 1 + tasmota/language/sk_SK.h | 1 + tasmota/language/sv_SE.h | 1 + tasmota/language/tr_TR.h | 1 + tasmota/language/uk_UA.h | 1 + tasmota/language/zh_CN.h | 1 + tasmota/language/zh_TW.h | 1 + tasmota/settings.ino | 5 +++++ tasmota/support_command.ino | 16 ++++++++++++---- tasmota/tasmota.h | 1 + tasmota/tasmota.ino | 2 +- tasmota/tasmota_version.h | 2 +- tasmota/xdrv_01_webserver.ino | 16 ++++++++++++---- 32 files changed, 62 insertions(+), 11 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7c99a0a6d..dbbe42f3a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -52,9 +52,10 @@ The following binary downloads have been compiled with ESP8266/Arduino library c ## Changelog -### Version 8.3.0.1 +### Version 8.3.0.2 - Change KNX pow function to approximative pow saving 5k of code space - Change Mutichannel Gas sensor pow function to approximative pow saving 5k of code space - Change Quick Power Cycle detection from 4 to 7 power interrupts (#4066) - Fix default state of ``SetOption73 0`` for button decoupling and send multi-press and hold MQTT messages +- Add command ``DeviceName`` defaults to FriendlyName1 and replaces FriendlyName1 in GUI diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 7f48c3918..f8fd969bc 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased (development) +### 8.3.0.2 20200517 + +- Add command ``DeviceName`` defaults to FriendlyName1 and replaces FriendlyName1 in GUI + ### 8.3.0.1 20200514 - Change KNX pow function to approximative pow saving 5k of code space diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 089a9b55f..e8286e585 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -276,6 +276,7 @@ #define D_WCFG_5_WAIT "Wait" #define D_WCFG_6_SERIAL "Serial" #define D_WCFG_7_WIFIMANAGER_RESET_ONLY "ManagerRst" +#define D_CMND_DEVICENAME "DeviceName" #define D_CMND_FRIENDLYNAME "FriendlyName" #define D_CMND_SWITCHMODE "SwitchMode" #define D_CMND_INTERLOCK "Interlock" diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index 03ae5bd6c..a40df8551 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Други параметри" #define D_TEMPLATE "Модел" #define D_ACTIVATE "Активирай" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Парола на уеб администратора" #define D_MQTT_ENABLE "Активиране на MQTT" #define D_FRIENDLY_NAME "Приятелско име" diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h index 71a907a81..33839ed4d 100644 --- a/tasmota/language/cs_CZ.h +++ b/tasmota/language/cs_CZ.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Další nastavení" #define D_TEMPLATE "Šablona" #define D_ACTIVATE "Aktivovat" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Heslo Web administrátora" #define D_MQTT_ENABLE "MQTT aktivní" #define D_FRIENDLY_NAME "Friendly Name" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index fdd1ec18d..9f676f101 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Sonstige Einstellungen" #define D_TEMPLATE "Vorlage" #define D_ACTIVATE "Aktivieren" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Passwort für Web Oberfläche" #define D_MQTT_ENABLE "MQTT aktivieren" #define D_FRIENDLY_NAME "Name [friendly name]" diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h index ddd1d5b0b..ec6275dbd 100644 --- a/tasmota/language/el_GR.h +++ b/tasmota/language/el_GR.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Άλλες παράμετροι" #define D_TEMPLATE "Πρότυπο" #define D_ACTIVATE "Ενεργοποίηση" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Κωδικός διαχειριστή" #define D_MQTT_ENABLE "Ενεργοποίηση MQTT" #define D_FRIENDLY_NAME "Φιλική ονομασία" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index 0396ca3e6..48208a0bb 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Other parameters" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Web Admin Password" #define D_MQTT_ENABLE "MQTT enable" #define D_FRIENDLY_NAME "Friendly Name" diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index 0602c3634..40b98f766 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Otros parámetros" #define D_TEMPLATE "Plantilla" #define D_ACTIVATE "Activar" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Clave Administrador Web" #define D_MQTT_ENABLE "Habilitar MQTT" #define D_FRIENDLY_NAME "Nombre Amigable" diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h index c620572ca..dbe9c597a 100644 --- a/tasmota/language/fr_FR.h +++ b/tasmota/language/fr_FR.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Autres paramètres" #define D_TEMPLATE "Modèle" #define D_ACTIVATE "Activer" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Mot de passe Web Admin" #define D_MQTT_ENABLE "MQTT activé" #define D_FRIENDLY_NAME "Surnom" diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h index 4802b529e..fe77159c1 100644 --- a/tasmota/language/he_HE.h +++ b/tasmota/language/he_HE.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "פרמטרים שונים" #define D_TEMPLATE "תבנית" #define D_ACTIVATE "הפעל" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "סיסמת מנהל" #define D_MQTT_ENABLE "MQTT אפשר" #define D_FRIENDLY_NAME "שם ידידותי" diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h index ee45ae083..844fa2f8f 100644 --- a/tasmota/language/hu_HU.h +++ b/tasmota/language/hu_HU.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Egyéb beállítások" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Web admin jelszó" #define D_MQTT_ENABLE "MQTT engedélyezése" #define D_FRIENDLY_NAME "Név" diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 88acefc92..5fa0d51dd 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Altri parametri" #define D_TEMPLATE "Modello" #define D_ACTIVATE "Attiva" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Password amministratore web" #define D_MQTT_ENABLE "Abilita MQTT" #define D_FRIENDLY_NAME "Nome amichevole" diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h index 3e3bd17e0..5ab580ce8 100644 --- a/tasmota/language/ko_KO.h +++ b/tasmota/language/ko_KO.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "기타 설정" #define D_TEMPLATE "템플릿" #define D_ACTIVATE "활성화" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Web Admin 비밀번호" #define D_MQTT_ENABLE "MQTT 사용" #define D_FRIENDLY_NAME "Friendly Name" diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h index 49efcb622..84ce656e3 100644 --- a/tasmota/language/nl_NL.h +++ b/tasmota/language/nl_NL.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Overige parameters" #define D_TEMPLATE "Sjabloon" #define D_ACTIVATE "Activeer" +#define D_DEVICE_NAME "Apparaatnaam" #define D_WEB_ADMIN_PASSWORD "Web Admin Wachtwoord" #define D_MQTT_ENABLE "MQTT ingeschakeld" #define D_FRIENDLY_NAME "Beschrijvende naam" diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h index d9d05fbeb..8ee529292 100644 --- a/tasmota/language/pl_PL.h +++ b/tasmota/language/pl_PL.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Inne parametry" #define D_TEMPLATE "Szablon" #define D_ACTIVATE "Aktywuj" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Hasło administratora" #define D_MQTT_ENABLE "Załącz MQTT" #define D_FRIENDLY_NAME "Nazwa" diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h index 4fb48c04d..39e7affb0 100644 --- a/tasmota/language/pt_BR.h +++ b/tasmota/language/pt_BR.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Outros parâmetros" #define D_TEMPLATE "Modelo" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Senha de WEB Admin" #define D_MQTT_ENABLE "MQTT habilitado" #define D_FRIENDLY_NAME "Nome amigável" diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h index 232b7494d..aabd353fe 100644 --- a/tasmota/language/pt_PT.h +++ b/tasmota/language/pt_PT.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Outros parametros" #define D_TEMPLATE "Modelo" #define D_ACTIVATE "Ativar" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Palavra Chave do Admin WEB" #define D_MQTT_ENABLE "MQTT habilitado" #define D_FRIENDLY_NAME "Nome amigável" diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h index 79c66d779..77a2240ca 100644 --- a/tasmota/language/ro_RO.h +++ b/tasmota/language/ro_RO.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Alți paramatri" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activare" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Parolă Web Admin" #define D_MQTT_ENABLE "Activare MQTT" #define D_FRIENDLY_NAME "Friendly Name" diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h index b7d8adcb6..0e5cd24ac 100644 --- a/tasmota/language/ru_RU.h +++ b/tasmota/language/ru_RU.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Параметры Прочие" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Пароль Web администратора" #define D_MQTT_ENABLE "MQTT активен" #define D_FRIENDLY_NAME "Дружественное Имя" diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h index 9db574bf8..d8bdd2ece 100644 --- a/tasmota/language/sk_SK.h +++ b/tasmota/language/sk_SK.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Ostatné nastavenia" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Heslo Web administrátora" #define D_MQTT_ENABLE "MQTT aktívne" #define D_FRIENDLY_NAME "Friendly Name" diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h index da8355215..64a540f96 100644 --- a/tasmota/language/sv_SE.h +++ b/tasmota/language/sv_SE.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Andra parametrar" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Webbadmin-lösenord" #define D_MQTT_ENABLE "MQTT aktivera" #define D_FRIENDLY_NAME "Läsbart namn" diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h index 9b205954b..c62981f8d 100644 --- a/tasmota/language/tr_TR.h +++ b/tasmota/language/tr_TR.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Diğer parametreler" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Web Yönetici Şifresi" #define D_MQTT_ENABLE "MQTT aktif" #define D_FRIENDLY_NAME "Kullanıcı Dostu İsim" diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h index 8c566eac3..0b90eaf53 100644 --- a/tasmota/language/uk_UA.h +++ b/tasmota/language/uk_UA.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "Параметри Інше" #define D_TEMPLATE "Шаблони" #define D_ACTIVATE "Активований" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "Гасло адміністратора Web" #define D_MQTT_ENABLE "MQTT активний" #define D_FRIENDLY_NAME "Дружня назва" diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h index 143e49aec..e521054f5 100644 --- a/tasmota/language/zh_CN.h +++ b/tasmota/language/zh_CN.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "其他设置" #define D_TEMPLATE "模板" #define D_ACTIVATE "启用" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "WEB 管理密码" #define D_MQTT_ENABLE "启用MQTT" #define D_FRIENDLY_NAME "昵称" diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h index 5c96ec688..bfa967b6d 100644 --- a/tasmota/language/zh_TW.h +++ b/tasmota/language/zh_TW.h @@ -302,6 +302,7 @@ #define D_OTHER_PARAMETERS "其他設置" #define D_TEMPLATE "Template" #define D_ACTIVATE "Activate" +#define D_DEVICE_NAME "Device Name" #define D_WEB_ADMIN_PASSWORD "WEB管理密碼" #define D_MQTT_ENABLE "啟用MQTT" #define D_FRIENDLY_NAME "昵稱" diff --git a/tasmota/settings.ino b/tasmota/settings.ino index b189e1729..5214add7a 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -754,6 +754,7 @@ void SettingsDefaultSet2(void) SettingsUpdateText(SET_FRIENDLYNAME2, PSTR(FRIENDLY_NAME"2")); SettingsUpdateText(SET_FRIENDLYNAME3, PSTR(FRIENDLY_NAME"3")); SettingsUpdateText(SET_FRIENDLYNAME4, PSTR(FRIENDLY_NAME"4")); + SettingsUpdateText(SET_DEVICENAME, SettingsText(SET_FRIENDLYNAME1)); SettingsUpdateText(SET_OTAURL, PSTR(OTA_URL)); // Power @@ -1411,6 +1412,10 @@ void SettingsDelta(void) if (Settings.rules[2][0] == 0) { Settings.rules[2][1] = 0; } } + if (Settings.version < 0x08030002) { + SettingsUpdateText(SET_DEVICENAME, SettingsText(SET_FRIENDLYNAME1)); + } + Settings.version = VERSION; SettingsSave(1); } diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 6e0a74c93..91434b019 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -25,7 +25,7 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|" D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|" D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" - D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|" + D_CMND_DEVICENAME "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|" D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|" D_CMND_HUMOFFSET "|" D_CMND_SPEEDUNIT "|" D_CMND_GLOBAL_TEMP "|" D_CMND_GLOBAL_HUM "|" #ifdef USE_I2C @@ -48,7 +48,7 @@ void (* const TasmotaCommand[])(void) PROGMEM = { &CmndModule, &CmndModules, &CmndGpio, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange, &CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig, &CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig, - &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd, + &CmndDevicename, &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd, &CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset, &CmndHumOffset, &CmndSpeedUnit, &CmndGlobalTemp, &CmndGlobalHum, #ifdef USE_I2C @@ -395,11 +395,11 @@ void CmndStatus(void) for (uint32_t i = 0; i < MAX_SWITCHES; i++) { snprintf_P(stemp2, sizeof(stemp2), PSTR("%s%s%d" ), stemp2, (i > 0 ? "," : ""), Settings.switchmode[i]); } - Response_P(PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_FRIENDLYNAME "\":[%s],\"" D_CMND_TOPIC "\":\"%s\",\"" + Response_P(PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_DEVICENAME "\":\"%s\",\"" D_CMND_FRIENDLYNAME "\":[%s],\"" D_CMND_TOPIC "\":\"%s\",\"" D_CMND_BUTTONTOPIC "\":\"%s\",\"" D_CMND_POWER "\":%d,\"" D_CMND_POWERONSTATE "\":%d,\"" D_CMND_LEDSTATE "\":%d,\"" D_CMND_LEDMASK "\":\"%04X\",\"" D_CMND_SAVEDATA "\":%d,\"" D_JSON_SAVESTATE "\":%d,\"" D_CMND_SWITCHTOPIC "\":\"%s\",\"" D_CMND_SWITCHMODE "\":[%s],\"" D_CMND_BUTTONRETAIN "\":%d,\"" D_CMND_SWITCHRETAIN "\":%d,\"" D_CMND_SENSORRETAIN "\":%d,\"" D_CMND_POWERRETAIN "\":%d}}"), - ModuleNr(), stemp, mqtt_topic, + ModuleNr(), SettingsText(SET_DEVICENAME), stemp, mqtt_topic, SettingsText(SET_MQTT_BUTTON_TOPIC), power, Settings.poweronstate, Settings.ledstate, Settings.ledmask, Settings.save_data, Settings.flag.save_state, // SetOption0 - Save power state and use after restart @@ -1488,6 +1488,14 @@ void CmndWifiConfig(void) Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_config, GetTextIndexed(stemp1, sizeof(stemp1), Settings.sta_config, kWifiConfig)); } +void CmndDevicename(void) +{ + if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { + SettingsUpdateText(SET_DEVICENAME, (SC_DEFAULT == Shortcut()) ? SettingsText(SET_FRIENDLYNAME1) : XdrvMailbox.data); + } + ResponseCmndChar(SettingsText(SET_DEVICENAME)); +} + void CmndFriendlyname(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_FRIENDLYNAMES)) { diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 3cb309910..0376f3429 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -292,6 +292,7 @@ enum SettingsTextIndex { SET_OTAURL, SET_MQTT_GRP_TOPIC2, SET_MQTT_GRP_TOPIC3, SET_MQTT_GRP_TOPIC4, SET_TEMPLATE_NAME, SET_DEV_GROUP_NAME1, SET_DEV_GROUP_NAME2, SET_DEV_GROUP_NAME3, SET_DEV_GROUP_NAME4, + SET_DEVICENAME, SET_MAX }; enum DevGroupMessageType { DGR_MSGTYP_FULL_STATUS, DGR_MSGTYP_PARTIAL_UPDATE, DGR_MSGTYP_UPDATE, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_MSGTYP_UPDATE_DIRECT, DGR_MSGTYPE_UPDATE_COMMAND }; diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 091b6eed3..ecf1127ec 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -307,7 +307,7 @@ void setup(void) { SetPowerOnState(); - AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_CORE_RELEASE), PROJECT, SettingsText(SET_FRIENDLYNAME1), my_version, my_image); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_CORE_RELEASE), PROJECT, SettingsText(SET_DEVICENAME), my_version, my_image); #ifdef FIRMWARE_MINIMAL AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION)); #endif // FIRMWARE_MINIMAL diff --git a/tasmota/tasmota_version.h b/tasmota/tasmota_version.h index e0fa3a3ce..d9f248e55 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 = 0x08030001; +const uint32_t VERSION = 0x08030002; // Lowest compatible version const uint32_t VERSION_COMPATIBLE = 0x07010006; diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 75f309569..3f7ad8ec0 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -455,6 +455,8 @@ const char HTTP_FORM_OTHER[] PROGMEM = "

" "
" "
" + "
" + "

" "
"; const char HTTP_FORM_END[] PROGMEM = @@ -849,7 +851,7 @@ void WSContentStart_P(const char* title, bool auth) WSContentBegin(200, CT_HTML); if (title != nullptr) { - WSContentSend_P(HTTP_HEADER1, SettingsText(SET_FRIENDLYNAME1), title); + WSContentSend_P(HTTP_HEADER1, SettingsText(SET_DEVICENAME), title); } } @@ -893,7 +895,7 @@ void WSContentSendStyle_P(const char* formatP, ...) WebColor(COL_TEXT_WARNING), #endif WebColor(COL_TITLE), - ModuleName().c_str(), SettingsText(SET_FRIENDLYNAME1)); + ModuleName().c_str(), SettingsText(SET_DEVICENAME)); if (Settings.flag3.gui_hostname_ip) { // SetOption53 - Show hostanme and IP address in GUI main menu bool lip = (static_cast(WiFi.localIP()) != 0); bool sip = (static_cast(WiFi.softAPIP()) != 0); @@ -1992,7 +1994,9 @@ void HandleOtherConfiguration(void) TemplateJson(); char stemp[strlen(mqtt_data) +1]; strlcpy(stemp, mqtt_data, sizeof(stemp)); // Get JSON template - WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", (Settings.flag.mqtt_enabled) ? " checked" : ""); // SetOption3 - Enable MQTT + WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", + (Settings.flag.mqtt_enabled) ? " checked" : "", // SetOption3 - Enable MQTT + SettingsText(SET_FRIENDLYNAME1), SettingsText(SET_DEVICENAME), SettingsText(SET_DEVICENAME)); uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; #ifdef USE_SONOFF_IFAN @@ -2042,6 +2046,9 @@ void OtherSaveSettings(void) char friendlyname[TOPSZ]; char message[LOGSZ]; + WebGetArg("dn", tmp, sizeof(tmp)); + SettingsUpdateText(SET_DEVICENAME, (!strlen(tmp)) ? SettingsText(SET_FRIENDLYNAME1) : tmp); + WebGetArg("wp", tmp, sizeof(tmp)); SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp); Settings.flag.mqtt_enabled = Webserver->hasArg("b1"); // SetOption3 - Enable MQTT @@ -2053,7 +2060,8 @@ void OtherSaveSettings(void) #endif // USE_EMULATION_WEMO || USE_EMULATION_HUE #endif // USE_EMULATION - snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation); + snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_DEVICENAME " %s, " D_CMND_FRIENDLYNAME), + GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation, SettingsText(SET_DEVICENAME)); for (uint32_t i = 0; i < MAX_FRIENDLYNAMES; i++) { snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i); WebGetArg(webindex, tmp, sizeof(tmp)); From 90e833238fcc130007e600ed3bf1dfafdf861588 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 17 May 2020 17:38:28 +0200 Subject: [PATCH 17/21] Allow no DeviceName --- tasmota/support_command.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 91434b019..dbcd5a954 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -1491,7 +1491,7 @@ void CmndWifiConfig(void) void CmndDevicename(void) { if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { - SettingsUpdateText(SET_DEVICENAME, (SC_DEFAULT == Shortcut()) ? SettingsText(SET_FRIENDLYNAME1) : XdrvMailbox.data); + SettingsUpdateText(SET_DEVICENAME, ('"' == XdrvMailbox.data[0]) ? "" : (SC_DEFAULT == Shortcut()) ? SettingsText(SET_FRIENDLYNAME1) : XdrvMailbox.data); } ResponseCmndChar(SettingsText(SET_DEVICENAME)); } From 4e38cb7ba9acae1db9f50330c10963e5044eeb41 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 17 May 2020 17:55:36 +0200 Subject: [PATCH 18/21] Allow empty DeviceName --- tasmota/xdrv_01_webserver.ino | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 3f7ad8ec0..a1e4a247b 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -456,7 +456,7 @@ const char HTTP_FORM_OTHER[] PROGMEM = "
" "
" "
" - "

" + "

" "
"; const char HTTP_FORM_END[] PROGMEM = @@ -1996,7 +1996,7 @@ void HandleOtherConfiguration(void) strlcpy(stemp, mqtt_data, sizeof(stemp)); // Get JSON template WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", (Settings.flag.mqtt_enabled) ? " checked" : "", // SetOption3 - Enable MQTT - SettingsText(SET_FRIENDLYNAME1), SettingsText(SET_DEVICENAME), SettingsText(SET_DEVICENAME)); + SettingsText(SET_FRIENDLYNAME1), SettingsText(SET_DEVICENAME)); uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; #ifdef USE_SONOFF_IFAN @@ -2047,8 +2047,7 @@ void OtherSaveSettings(void) char message[LOGSZ]; WebGetArg("dn", tmp, sizeof(tmp)); - SettingsUpdateText(SET_DEVICENAME, (!strlen(tmp)) ? SettingsText(SET_FRIENDLYNAME1) : tmp); - + SettingsUpdateText(SET_DEVICENAME, (!strlen(tmp)) ? "" : (!strcmp(tmp,"1")) ? SettingsText(SET_FRIENDLYNAME1) : tmp); WebGetArg("wp", tmp, sizeof(tmp)); SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp); Settings.flag.mqtt_enabled = Webserver->hasArg("b1"); // SetOption3 - Enable MQTT From 20b2257c8c3911b729dd0104018fd0db7aba7991 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 17 May 2020 18:05:17 +0200 Subject: [PATCH 19/21] Unishox code optimized --- lib/Unishox-1.0-shadinger/src/unishox.cpp | 292 ++++++++++------------ lib/Unishox-1.0-shadinger/src/unishox.h | 40 ++- tasmota/my_user_config.h | 2 +- tasmota/xdrv_10_rules.ino | 9 +- 4 files changed, 174 insertions(+), 169 deletions(-) diff --git a/lib/Unishox-1.0-shadinger/src/unishox.cpp b/lib/Unishox-1.0-shadinger/src/unishox.cpp index 72c9d3915..4b17dce2e 100644 --- a/lib/Unishox-1.0-shadinger/src/unishox.cpp +++ b/lib/Unishox-1.0-shadinger/src/unishox.cpp @@ -153,52 +153,47 @@ const uint16_t BIN_CODE_TASMOTA_LEN = 3; // uint16_t mask[] PROGMEM = {0x8000, 0xC000, 0xE000, 0xF000, 0xF800, 0xFC00, 0xFE00, 0xFF00}; uint8_t mask[] PROGMEM = {0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF}; -int append_bits(char *out, size_t ol, unsigned int code, int clen, byte state) { - byte cur_bit; - byte blen; - unsigned char a_byte; - if (state == SHX_STATE_2) { - // remove change state prefix - if ((code >> 9) == 0x1C) { - code <<= 7; - clen -= 7; +void Unishox::append_bits(unsigned int code, int clen) { + + byte cur_bit; + byte blen; + unsigned char a_byte; + + if (state == SHX_STATE_2) { + // remove change state prefix + if ((code >> 9) == 0x1C) { + code <<= 7; + clen -= 7; + } + } + while (clen > 0) { + cur_bit = ol % 8; + blen = (clen > 8 ? 8 : clen); + a_byte = (code >> 8) & pgm_read_word(&mask[blen - 1]); + a_byte >>= cur_bit; + if (blen + cur_bit > 8) + blen = (8 - cur_bit); + if (out) { // if out == nullptr, then we are in dry-run mode + if (cur_bit == 0) + out[ol >> 3] = a_byte; + else + out[ol >> 3] |= a_byte; + } + code <<= blen; + ol += blen; + if ((out) && (0 == ol % 8)) { // if out == nullptr, dry-run mode. We miss the escaping of characters in the length + // we completed a full byte + char last_c = out[(ol / 8) - 1]; + if ((0 == last_c) || (ESCAPE_MARKER == last_c)) { + out[ol >> 3] = 1 + last_c; // increment to 0x01 or 0x2B + out[(ol >>3) -1] = ESCAPE_MARKER; // replace old value with marker + ol += 8; // add one full byte } - //if (code == 14272 && clen == 10) { - // code = 9084; - // clen = 14; - //} - } - while (clen > 0) { - cur_bit = ol % 8; - blen = (clen > 8 ? 8 : clen); - // a_byte = (code & pgm_read_word(&mask[blen - 1])) >> 8; - // a_byte = (code & (pgm_read_word(&mask[blen - 1]) << 8)) >> 8; - a_byte = (code >> 8) & pgm_read_word(&mask[blen - 1]); - a_byte >>= cur_bit; - if (blen + cur_bit > 8) - blen = (8 - cur_bit); - if (out) { // if out == nullptr, then we are in dry-run mode - if (cur_bit == 0) - out[ol / 8] = a_byte; - else - out[ol / 8] |= a_byte; - } - code <<= blen; - ol += blen; - if ((out) && (0 == ol % 8)) { // if out == nullptr, dry-run mode. We miss the escaping of characters in the length - // we completed a full byte - char last_c = out[(ol / 8) - 1]; - if ((0 == last_c) || (ESCAPE_MARKER == last_c)) { - out[ol / 8] = 1 + last_c; // increment to 0x01 or 0x2B - out[(ol / 8) -1] = ESCAPE_MARKER; // replace old value with marker - ol += 8; // add one full byte - } - } - clen -= blen; - } - return ol; + } + clen -= blen; + } } // First five bits are code and Last three bits of codes represent length @@ -210,40 +205,36 @@ byte codes[] PROGMEM = { 0x82, 0xC3, 0xE5, 0xED, 0xF5 }; byte bit_len[] PROGMEM = { 5, 7, 9, 12, 16 }; // uint16_t adder[7] PROGMEM = { 0, 32, 160, 672, 4768 }; // no more used -int encodeCount(char *out, int ol, int count) { +void Unishox::encodeCount(int32_t count) { int till = 0; int base = 0; - for (int i = 0; i < sizeof(bit_len); i++) { + for (uint32_t i = 0; i < sizeof(bit_len); i++) { uint32_t bit_len_i = pgm_read_byte(&bit_len[i]); till += (1 << bit_len_i); if (count < till) { byte codes_i = pgm_read_byte(&codes[i]); - ol = append_bits(out, ol, (codes_i & 0xF8) << 8, codes_i & 0x07, 1); + append_bits((codes_i & 0xF8) << 8, codes_i & 0x07); // ol = append_bits(out, ol, (count - pgm_read_word(&adder[i])) << (16 - bit_len_i), bit_len_i, 1); - ol = append_bits(out, ol, (count - base) << (16 - bit_len_i), bit_len_i, 1); - return ol; + append_bits((count - base) << (16 - bit_len_i), bit_len_i); + return; } base = till; } - return ol; + return; } -int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *state, byte *is_all_upper) { - int j, k; - int longest_dist = 0; - int longest_len = 0; +bool Unishox::matchOccurance(void) { + int32_t j, k; + uint32_t longest_dist = 0; + uint32_t longest_len = 0; for (j = l - NICE_LEN; j >= 0; j--) { for (k = l; k < len && j + k - l < l; k++) { if (in[k] != in[j + k - l]) break; } - // while ((((unsigned char) in[k]) >> 6) == 2) - // k--; // Skip partial UTF-8 matches - //if ((in[k - 1] >> 3) == 0x1E || (in[k - 1] >> 4) == 0x0E || (in[k - 1] >> 5) == 0x06) - // k--; if (k - l > NICE_LEN - 1) { - int match_len = k - l - NICE_LEN; - int match_dist = l - j - NICE_LEN + 1; + uint32_t match_len = k - l - NICE_LEN; + uint32_t match_dist = l - j - NICE_LEN + 1; if (match_len > longest_len) { longest_len = match_len; longest_dist = match_dist; @@ -251,19 +242,18 @@ int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *sta } } if (longest_len) { - if (*state == SHX_STATE_2 || *is_all_upper) { - *is_all_upper = 0; - *state = SHX_STATE_1; - *ol = append_bits(out, *ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, *state); + if (state == SHX_STATE_2 || is_all_upper) { + is_all_upper = 0; + state = SHX_STATE_1; + append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN); } - *ol = append_bits(out, *ol, DICT_CODE, DICT_CODE_LEN, 1); - *ol = encodeCount(out, *ol, longest_len); - *ol = encodeCount(out, *ol, longest_dist); - l += (longest_len + NICE_LEN); - l--; - return l; + append_bits(DICT_CODE, DICT_CODE_LEN); + encodeCount(longest_len); + encodeCount(longest_dist); + l += longest_len + NICE_LEN - 1; + return true; } - return -l; + return false; } // Compress a buffer. @@ -275,15 +265,18 @@ int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *sta // Output: // - if >= 0: size of the compressed buffer. The output buffer does not contain NULL bytes, and it is not NULL terminated // - if < 0: an error occured, most certainly the output buffer was not large enough -int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) { +int32_t Unishox::unishox_compress(const char *p_in, size_t p_len, char *p_out, size_t p_len_out) { + in = p_in; + len = p_len; + out = p_out; + len_out = p_len_out; char *ptr; byte bits; - byte state; - int l, ll, ol; + int ll; char c_in, c_next; - byte is_upper, is_all_upper; + byte is_upper; ol = 0; state = SHX_STATE_1; @@ -302,23 +295,20 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) if (state == SHX_STATE_2 || is_all_upper) { is_all_upper = 0; state = SHX_STATE_1; - ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state); // back to lower case and Set1 + append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN); // back to lower case and Set1 } // ol = append_bits(out, ol, RPT_CODE, RPT_CODE_LEN, 1); - ol = append_bits(out, ol, RPT_CODE_TASMOTA, RPT_CODE_TASMOTA_LEN, 1); // reusing CRLF for RPT - ol = encodeCount(out, ol, rpt_count - 4); - l += rpt_count; - l--; + append_bits(RPT_CODE_TASMOTA, RPT_CODE_TASMOTA_LEN); // reusing CRLF for RPT + encodeCount(rpt_count - 4); + l += rpt_count - 1; continue; } } if (l < (len - NICE_LEN + 1)) { - l = matchOccurance(in, len, l, out, &ol, &state, &is_all_upper); - if (l > 0) { - continue; - } - l = -l; + if (matchOccurance()) { + continue; + } } if (state == SHX_STATE_2) { // if Set2 if ((c_in >= ' ' && c_in <= '@') || @@ -326,7 +316,7 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) (c_in >= '{' && c_in <= '~')) { } else { state = SHX_STATE_1; // back to Set1 and lower case - ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state); + append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN); } } @@ -336,7 +326,7 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) else { if (is_all_upper) { is_all_upper = 0; - ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state); + append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN); } } @@ -351,37 +341,30 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) break; } if (ll == l-1) { - ol = append_bits(out, ol, ALL_UPPER_CODE, ALL_UPPER_CODE_LEN, state); // CapsLock + append_bits(ALL_UPPER_CODE, ALL_UPPER_CODE_LEN); // CapsLock is_all_upper = 1; } } if (state == SHX_STATE_1 && c_in >= '0' && c_in <= '9') { - ol = append_bits(out, ol, SW2_STATE2_CODE, SW2_STATE2_CODE_LEN, state); // Switch to sticky Set2 + append_bits(SW2_STATE2_CODE, SW2_STATE2_CODE_LEN); // Switch to sticky Set2 state = SHX_STATE_2; } c_in -= 32; if (is_all_upper && is_upper) c_in += 32; if (c_in == 0 && state == SHX_STATE_2) - ol = append_bits(out, ol, ST2_SPC_CODE, ST2_SPC_CODE_LEN, state); // space from Set2 ionstead of Set1 + append_bits(ST2_SPC_CODE, ST2_SPC_CODE_LEN); // space from Set2 ionstead of Set1 else { - // ol = append_bits(out, ol, pgm_read_word(&c_95[c_in]), pgm_read_byte(&l_95[c_in]), state); // original version with c/l in split arrays uint16_t cl = pgm_read_word(&cl_95[c_in]); - ol = append_bits(out, ol, cl & 0xFFF0, cl & 0x000F, state); + append_bits(cl & 0xFFF0, cl & 0x000F); } - } else - // if (c_in == 13 && c_next == 10) { // CRLF disabled - // ol = append_bits(out, ol, CRLF_CODE, CRLF_CODE_LEN, state); // CRLF - // l++; - // } else - if (c_in == 10) { - ol = append_bits(out, ol, LF_CODE, LF_CODE_LEN, state); // LF - } else - if (c_in == '\t') { - ol = append_bits(out, ol, TAB_CODE, TAB_CODE_LEN, state); // TAB + } else if (c_in == 10) { + append_bits(LF_CODE, LF_CODE_LEN); // LF + } else if (c_in == '\t') { + append_bits(TAB_CODE, TAB_CODE_LEN); // TAB } else { - ol = append_bits(out, ol, BIN_CODE_TASMOTA, BIN_CODE_TASMOTA_LEN, state); // Binary, we reuse the Unicode marker which 3 bits instead of 9 - ol = encodeCount(out, ol, (unsigned char) 255 - c_in); + append_bits(BIN_CODE_TASMOTA, BIN_CODE_TASMOTA_LEN); // Binary, we reuse the Unicode marker which 3 bits instead of 9 + encodeCount((unsigned char) 255 - c_in); } // check that we have some headroom in the output buffer @@ -392,50 +375,46 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) bits = ol % 8; if (bits) { - ol = append_bits(out, ol, TERM_CODE, 8 - bits, 1); // 0011 0111 1100 0000 TERM = 0011 0111 11 + state = SHX_STATE_1; + append_bits(TERM_CODE, 8 - bits); // 0011 0111 1100 0000 TERM = 0011 0111 11 } return ol/8+(ol%8?1:0); } -int getBitVal(const char *in, int bit_no, int count) { - char c_in = in[bit_no >> 3]; - if ((bit_no >> 3) && (ESCAPE_MARKER == in[(bit_no >> 3) - 1])) { // if previous byte is a marker, decrement - c_in--; +uint32_t Unishox::getNextBit(void) { + if (8 == bit_no) { + byte_in = in[byte_no++]; + if (ESCAPE_MARKER == byte_in) { + byte_in = in[byte_no++] - 1; + } + bit_no = 0; } - return (c_in & (0x80 >> (bit_no % 8)) ? 1 << count : 0); + return byte_in & (0x80 >> bit_no++) ? 1 : 0; } // Returns: // 0..11 // or -1 if end of stream -int getCodeIdx(char *code_type, const char *in, int len, int *bit_no_p) { - int code = 0; - int count = 0; +int32_t Unishox::getCodeIdx(const char *code_type) { + int32_t code = 0; + int32_t count = 0; do { - // detect marker - if (ESCAPE_MARKER == in[*bit_no_p >> 3]) { - *bit_no_p += 8; // skip marker - } - if (*bit_no_p >= len) + if (bit_no >= len) return -1; // invalid state - code += getBitVal(in, *bit_no_p, count); - (*bit_no_p)++; + code += getNextBit() << count; count++; uint8_t code_type_code = pgm_read_byte(&code_type[code]); if (code_type_code && (code_type_code & 0x07) == count) { return code_type_code >> 3; } } while (count < 5); - return 1; // skip if code not found + return -1; // skip if code not found } -int getNumFromBits(const char *in, int bit_no, int count) { +int32_t Unishox::getNumFromBits(uint32_t count) { int ret = 0; while (count--) { - if (ESCAPE_MARKER == in[bit_no >> 3]) { - bit_no += 8; // skip marker - } - ret += getBitVal(in, bit_no++, count); + ret += getNextBit() << count; } return ret; } @@ -452,8 +431,8 @@ int getNumFromBits(const char *in, int bit_no, int count) { // uint16_t adder_read[] PROGMEM = {0, 32, 160, 672, 4768 }; // Code size optimized, recalculate adder[] like in encodeCount -int readCount(const char *in, int *bit_no_p, int len) { - int idx = getCodeIdx(us_hcode, in, len, bit_no_p); +uint32_t Unishox::readCount(void) { + int32_t idx = getCodeIdx(us_hcode); if (idx >= 1) idx--; // we skip v = 1 (code '0') since we no more accept 2 bits encoding if ((idx >= sizeof(bit_len)) || (idx < 0)) return 0; // unsupported or end of stream @@ -465,44 +444,41 @@ int readCount(const char *in, int *bit_no_p, int len) { bit_len_idx = pgm_read_byte(&bit_len[i]); till += (1 << bit_len_idx); } - int count = getNumFromBits(in, *bit_no_p, bit_len_idx) + base; + int count = getNumFromBits(bit_len_idx) + base; - (*bit_no_p) += bit_len_idx; return count; } -int decodeRepeat(const char *in, int len, char *out, int ol, int *bit_no) { - int dict_len = readCount(in, bit_no, len) + NICE_LEN; - int dist = readCount(in, bit_no, len) + NICE_LEN - 1; +void Unishox::decodeRepeat(void) { + uint32_t dict_len = readCount() + NICE_LEN; + uint32_t dist = readCount() + NICE_LEN - 1; memcpy(out + ol, out + ol - dist, dict_len); ol += dict_len; - - return ol; } -int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out) { +int32_t Unishox::unishox_decompress(const char *p_in, size_t p_len, char *p_out, size_t p_len_out) { + in = p_in; + len = p_len; + out = p_out; + len_out = p_len_out; - int dstate; - int bit_no; - byte is_all_upper; - - int ol = 0; - bit_no = 0; + ol = 0; + bit_no = 8; // force load of first byte, pretending we expired the last one + byte_no = 0; dstate = SHX_SET1; is_all_upper = 0; len <<= 3; // *8, len in bits out[ol] = 0; while (bit_no < len) { - int h, v; + int32_t h, v; char c = 0; byte is_upper = is_all_upper; - int orig_bit_no = bit_no; - v = getCodeIdx(us_vcode, in, len, &bit_no); // read vCode + v = getCodeIdx(us_vcode); // read vCode if (v < 0) break; // end of stream h = dstate; // Set1 or Set2 if (v == 0) { // Switch which is common to Set1 and Set2, first entry - h = getCodeIdx(us_hcode, in, len, &bit_no); // read hCode + h = getCodeIdx(us_hcode); // read hCode if (h < 0) break; // end of stream if (h == SHX_SET1) { // target is Set1 if (dstate == SHX_SET1) { // Switch from Set1 to Set1 us UpperCase @@ -510,10 +486,10 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out is_upper = is_all_upper = 0; continue; } - v = getCodeIdx(us_vcode, in, len, &bit_no); // read again vCode + v = getCodeIdx(us_vcode); // read again vCode if (v < 0) break; // end of stream if (v == 0) { - h = getCodeIdx(us_hcode, in, len, &bit_no); // read second hCode + h = getCodeIdx(us_hcode); // read second hCode if (h < 0) break; // end of stream if (h == SHX_SET1) { // If double Switch Set1, the CapsLock is_all_upper = 1; @@ -532,23 +508,23 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out continue; } if (h != SHX_SET1) { // all other Sets (why not else) - v = getCodeIdx(us_vcode, in, len, &bit_no); // we changed set, now read vCode for char + v = getCodeIdx(us_vcode); // we changed set, now read vCode for char if (v < 0) break; // end of stream } } if (v == 0 && h == SHX_SET1A) { if (is_upper) { - out[ol++] = 255 - readCount(in, &bit_no, len); // binary + out[ol++] = 255 - readCount(); // binary } else { - ol = decodeRepeat(in, len, out, ol, &bit_no); // dist + decodeRepeat(); // dist } continue; } if (h == SHX_SET1 && v == 3) { // was Unicode, will do Binary instead - out[ol++] = 255 - readCount(in, &bit_no, len); // binary + out[ol++] = 255 - readCount(); // binary continue; } if (h < 7 && v < 11) // TODO: are these the actual limits? Not 11x7 ? @@ -561,22 +537,11 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out c = '\t'; // If UpperCase Space, change to TAB if (h == SHX_SET1B) { if (8 == v) { // was LF or RPT, now only LF - // if (is_upper) { // rpt - // int count = readCount(in, &bit_no, len); - // count += 4; - // char rpt_c = out[ol - 1]; - // while (count--) - // out[ol++] = rpt_c; - // } else { out[ol++] = '\n'; - // } continue; } if (9 == v) { // was CRLF, now RPT - // out[ol++] = '\r'; // CRLF removed - // out[ol++] = '\n'; - int count = readCount(in, &bit_no, len); - count += 4; + uint32_t count = readCount() + 4; if (ol + count >= len_out) { return -1; // overflow } @@ -598,5 +563,4 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out } return ol; - } diff --git a/lib/Unishox-1.0-shadinger/src/unishox.h b/lib/Unishox-1.0-shadinger/src/unishox.h index 4d6b81641..d1cda8976 100644 --- a/lib/Unishox-1.0-shadinger/src/unishox.h +++ b/lib/Unishox-1.0-shadinger/src/unishox.h @@ -20,7 +20,45 @@ #define unishox extern int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out); -extern int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out); +//extern int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out); + +class Unishox { + +public: + Unishox() {}; + + int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out); + int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out); + +private: + + void append_bits(unsigned int code, int clen); + void encodeCount(int32_t count); + bool matchOccurance(void); + + uint32_t getNextBit(void); + int32_t getCodeIdx(const char *code_type); + uint32_t readCount(void); + void decodeRepeat(void); + int32_t getNumFromBits(uint32_t count); + + inline void writeOut(char c) { out[ol++] = c; } + + int32_t l; + uint32_t ol; + int32_t bit_no; + uint32_t byte_no; + const char * in; + char * out; + size_t len; + size_t len_out; + + uint8_t dstate; + unsigned char byte_in; + uint8_t state; + uint8_t is_all_upper; + +}; #endif diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 33d47d57f..9b04a2ea2 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -397,7 +397,7 @@ // -- Rules or Script ---------------------------- // Select none or only one of the below defines USE_RULES or USE_SCRIPT #define USE_RULES // Add support for rules (+8k code) - #define USE_RULES_COMPRESSION // Compresses rules in Flash at about ~50% (+3.8k code) + #define USE_RULES_COMPRESSION // Compresses rules in Flash at about ~50% (+3.3k code) //#define USE_SCRIPT // Add support for script (+17k code) //#define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index 922014e49..ddb4de39d 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -213,6 +213,7 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; #ifdef USE_RULES_COMPRESSION // Statically allocate one String per rule String k_rules[MAX_RULE_SETS] = { String(), String(), String() }; // Strings are created empty +Unishox compressor; // singleton #endif // USE_RULES_COMPRESSION // Returns whether the rule is uncompressed, which means the first byte is not NULL @@ -256,6 +257,7 @@ size_t GetRuleLenStorage(uint32_t idx) { #endif } +#ifdef USE_RULES_COMPRESSION // internal function, do the actual decompression void GetRule_decompress(String &rule, const char *rule_head) { size_t buf_len = 1 + *rule_head * 8; // the first byte contains size of buffer for uncompressed rule / 8, buf_len may overshoot by 7 @@ -268,12 +270,13 @@ void GetRule_decompress(String &rule, const char *rule_head) { rule.reserve(buf_len); char* buf = rule.begin(); - int32_t len_decompressed = unishox_decompress(rule_head, strlen(rule_head), buf, buf_len); + int32_t len_decompressed = compressor.unishox_decompress(rule_head, strlen(rule_head), buf, buf_len); buf[len_decompressed] = 0; // add NULL terminator // AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Rawdecompressed: %d"), len_decompressed); rule = buf; // assign the raw string to the String object (in reality re-writing the same data in the same place) } +#endif // USE_RULES_COMPRESSION // // Read rule in memory, uncompress if needed @@ -308,7 +311,7 @@ String GetRule(uint32_t idx) { // If out == nullptr, we are in dry-run mode, so don't keep rule in cache int32_t SetRule_compress(uint32_t idx, const char *in, size_t in_len, char *out, size_t out_len) { int32_t len_compressed; - len_compressed = unishox_compress(in, in_len, out, out_len); + len_compressed = compressor.unishox_compress(in, in_len, out, out_len); if (len_compressed >= 0) { // negative means compression failed because of buffer too small, we leave the rule untouched // check if we need to store in cache @@ -357,7 +360,7 @@ int32_t SetRule(uint32_t idx, const char *content, bool append = false) { int32_t len_compressed, len_uncompressed; len_uncompressed = strlen(Settings.rules[idx]); - len_compressed = unishox_compress(Settings.rules[idx], len_uncompressed, nullptr /* dry-run */, MAX_RULE_SIZE + 8); + len_compressed = compressor.unishox_compress(Settings.rules[idx], len_uncompressed, nullptr /* dry-run */, MAX_RULE_SIZE + 8); AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Stored uncompressed, would compress from %d to %d (-%d%%)"), len_uncompressed, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_uncompressed, 0, 100)); } From 1460f814901d1602d89c5d89c585ca4cf55e0848 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 17 May 2020 18:33:42 +0200 Subject: [PATCH 20/21] Zigbee allow zero shortaddr --- tasmota/xdrv_23_zigbee_0_constants.ino | 2 + tasmota/xdrv_23_zigbee_2_devices.ino | 48 ++++++------- tasmota/xdrv_23_zigbee_4_persistence.ino | 45 ------------- tasmota/xdrv_23_zigbee_5_converters.ino | 13 +--- tasmota/xdrv_23_zigbee_6_commands.ino | 13 ++-- tasmota/xdrv_23_zigbee_7_statemachine.ino | 29 +------- tasmota/xdrv_23_zigbee_8_parsers.ino | 82 +++++++++++------------ tasmota/xdrv_23_zigbee_9_impl.ino | 58 +++++++--------- 8 files changed, 101 insertions(+), 189 deletions(-) diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index 2aeade28d..28628f93c 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -24,6 +24,8 @@ typedef uint64_t Z_IEEEAddress; typedef uint16_t Z_ShortAddress; +const uint16_t BAD_SHORTADDR = 0xFFFE; + enum ZnpCommandType { Z_POLL = 0x00, Z_SREQ = 0x20, diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index 76413e23e..74d52c73e 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -103,7 +103,7 @@ public: // Probe the existence of device keys // Results: // - 0x0000 = not found - // - 0xFFFF = bad parameter + // - BAD_SHORTADDR = bad parameter // - 0x = the device's short address uint16_t isKnownShortAddr(uint16_t shortaddr) const; uint16_t isKnownLongAddr(uint64_t longaddr) const; @@ -295,18 +295,16 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) { // Scan all devices to find a corresponding shortaddr // Looks info device.shortaddr entry // In: -// shortaddr (non null) +// shortaddr (not BAD_SHORTADDR) // Out: // index in _devices of entry, -1 if not found // int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const { - if (!shortaddr) { return -1; } // does not make sense to look for 0x0000 shortaddr (localhost) + if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast) int32_t found = 0; - if (shortaddr) { - for (auto &elem : _devices) { - if (elem->shortaddr == shortaddr) { return found; } - found++; - } + for (auto &elem : _devices) { + if (elem->shortaddr == shortaddr) { return found; } + found++; } return -1; } @@ -321,11 +319,9 @@ int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const { int32_t Z_Devices::findLongAddr(uint64_t longaddr) const { if (!longaddr) { return -1; } int32_t found = 0; - if (longaddr) { - for (auto &elem : _devices) { - if (elem->longaddr == longaddr) { return found; } - found++; - } + for (auto &elem : _devices) { + if (elem->longaddr == longaddr) { return found; } + found++; } return -1; } @@ -358,7 +354,7 @@ uint16_t Z_Devices::isKnownShortAddr(uint16_t shortaddr) const { if (found >= 0) { return shortaddr; } else { - return 0; // unknown + return BAD_SHORTADDR; // unknown } } @@ -368,7 +364,7 @@ uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const { const Z_Device & device = devicesAt(found); return device.shortaddr; // can be zero, if not yet registered } else { - return 0; + return BAD_SHORTADDR; } } @@ -377,18 +373,18 @@ uint16_t Z_Devices::isKnownIndex(uint32_t index) const { const Z_Device & device = devicesAt(index); return device.shortaddr; } else { - return 0; + return BAD_SHORTADDR; } } uint16_t Z_Devices::isKnownFriendlyName(const char * name) const { - if ((!name) || (0 == strlen(name))) { return 0xFFFF; } // Error + if ((!name) || (0 == strlen(name))) { return BAD_SHORTADDR; } // Error int32_t found = findFriendlyName(name); if (found >= 0) { const Z_Device & device = devicesAt(found); return device.shortaddr; // can be zero, if not yet registered } else { - return 0; + return BAD_SHORTADDR; } } @@ -398,10 +394,10 @@ uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const { } // -// We have a seen a shortaddr on the network, get the corresponding +// We have a seen a shortaddr on the network, get the corresponding device object // Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) { - if (!shortaddr) { return *(Z_Device*) nullptr; } // this is not legal + if (BAD_SHORTADDR == shortaddr) { return *(Z_Device*) nullptr; } // this is not legal int32_t found = findShortAddr(shortaddr); if (found >= 0) { return *(_devices[found]); @@ -411,7 +407,7 @@ Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) { } // Same version but Const const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const { - if (!shortaddr) { return *(Z_Device*) nullptr; } // this is not legal + if (BAD_SHORTADDR == shortaddr) { return *(Z_Device*) nullptr; } // this is not legal int32_t found = findShortAddr(shortaddr); if (found >= 0) { return *(_devices[found]); @@ -471,7 +467,7 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { dirty(); } else { // neither short/lonf addr are found. - if (shortaddr || longaddr) { + if ((BAD_SHORTADDR != shortaddr) || longaddr) { createDeviceEntry(shortaddr, longaddr); } } @@ -481,7 +477,6 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { // Clear all endpoints // void Z_Devices::clearEndpoints(uint16_t shortaddr) { - if (!shortaddr) { return; } Z_Device &device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found @@ -495,7 +490,6 @@ void Z_Devices::clearEndpoints(uint16_t shortaddr) { // Add an endpoint to a shortaddr // void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) { - if (!shortaddr) { return; } if (0x00 == endpoint) { return; } Z_Device &device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found @@ -922,7 +916,7 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know char dataBuf[param_len + 1]; strcpy(dataBuf, param); RemoveSpace(dataBuf); - uint16_t shortaddr = 0; + uint16_t shortaddr = BAD_SHORTADDR; // start with unknown if (strlen(dataBuf) < 4) { // simple number 0..99 @@ -1018,8 +1012,8 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { uint16_t shortaddr = device.shortaddr; char hex[22]; - // ignore non-current device, if specified device is non-zero - if ((status_shortaddr) && (status_shortaddr != shortaddr)) { continue; } + // ignore non-current device, if device specified + if ((BAD_SHORTADDR != status_shortaddr) && (status_shortaddr != shortaddr)) { continue; } JsonObject& dev = devices.createNestedObject(); diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino index 78cb79bec..ef67221c9 100644 --- a/tasmota/xdrv_23_zigbee_4_persistence.ino +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -66,41 +66,6 @@ public: const static uint32_t ZIGB_NAME = 0x3167697A; // 'zig1' little endian const static size_t Z_MAX_FLASH = z_block_len - sizeof(z_flashdata_t); // 2040 -// encoding for the most commonly 32 clusters, used for binary encoding -const uint16_t Z_ClusterNumber[] PROGMEM = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, - 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0100, 0x0101, 0x0102, - 0x0201, 0x0202, 0x0203, 0x0204, - 0x0300, 0x0301, - 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, - 0x0500, 0x0501, 0x0502, - 0x0700, 0x0701, 0x0702, - 0x0B00, 0x0B01, 0x0B02, 0x0B03, 0x0B04, 0x0B05, - 0x1000, - 0xFC0F, -}; - -// convert a 1 byte cluster code to the actual cluster number -uint16_t fromClusterCode(uint8_t c) { - if (c >= ARRAY_SIZE(Z_ClusterNumber)) { - return 0xFFFF; // invalid - } - return pgm_read_word(&Z_ClusterNumber[c]); -} - -// convert a cluster number to 1 byte, or 0xFF if not in table -uint8_t toClusterCode(uint16_t c) { - for (uint32_t i = 0; i < ARRAY_SIZE(Z_ClusterNumber); i++) { - if (c == pgm_read_word(&Z_ClusterNumber[i])) { - return i; - } - } - return 0xFF; // not found -} class SBuffer hibernateDevice(const struct Z_Device &device) { SBuffer buf(128); @@ -202,18 +167,8 @@ void hydrateDevices(const SBuffer &buf) { for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) { uint32_t dev_record_len = buf.get8(k); -// AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Device %d Before Memory = %d // DIFF %d // record_len %d"), i, ESP_getFreeHeap(), before - ESP_getFreeHeap(), dev_record_len); -// before = ESP_getFreeHeap(); - SBuffer buf_d = buf.subBuffer(k, dev_record_len); -// char *hex_char = (char*) malloc((dev_record_len * 2) + 2); -// if (hex_char) { -// AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "/// SUB %s"), -// ToHex_P(buf_d.getBuffer(), dev_record_len, hex_char, (dev_record_len * 2) + 2)); -// free(hex_char); -// } - uint32_t d = 1; // index in device buffer uint16_t shortaddr = buf_d.get16(d); d += 2; uint64_t longaddr = buf_d.get64(d); d += 8; diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 07b5f5ef1..dfb69d4a7 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -79,16 +79,6 @@ uint8_t Z_getDatatypeLen(uint8_t t) { } } -// typedef struct Z_DataTypeMapping { -// uint8_t datatype; -// uint8_t len; // len in bytes and add 0x80 if DISCRETE -// } - -// const Z_DataTypeMapping Z_types[] PROGMEM = { -// { Znodata, 0 }, -// { Zdata8, 0 }, -// }; - typedef union ZCLHeaderFrameControl_t { struct { uint8_t frame_type : 2; // 00 = across entire profile, 01 = cluster specific @@ -572,6 +562,9 @@ typedef struct Z_AttributeConverter { Z_AttrConverter func; } Z_AttributeConverter; +// Cluster numbers are store in 8 bits format to save space, +// the following tables allows the conversion from 8 bits index Cx... +// to the 16 bits actual cluster number enum Cx_cluster_short { Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007, Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F, diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index e27f37f7a..949f4acb8 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -177,6 +177,9 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus break; } if (attrs) { + if (groupaddr) { + shortaddr = BAD_SHORTADDR; // if group address, don't send to device + } ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, 0, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr)); } } @@ -184,7 +187,7 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus // This callback is registered after a an attribute read command was made to a light, and fires if we don't get any response after 1000 ms int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { - if (shortaddr) { + if (BAD_SHORTADDR != shortaddr) { zigbee_devices.setReachable(shortaddr, false); // mark device as reachable } } @@ -208,7 +211,7 @@ void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus } if (wait_ms) { zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback); - if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group + if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable); } } @@ -314,12 +317,12 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin } if (z_cat >= 0) { uint8_t endpoint = 0; - if (shortaddr) { + if (BAD_SHORTADDR != shortaddr) { endpoint = zigbee_devices.findFirstEndpoint(shortaddr); } - if ((!shortaddr) || (endpoint)) { // send if group address or endpoint is known + if ((BAD_SHORTADDR == shortaddr) || (endpoint)) { // send if group address or endpoint is known zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, z_cat, 0 /* value */, &Z_ReadAttrCallback); - if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group + if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable); } diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 3531ba0a7..590c795ac 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -584,18 +584,6 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_WAIT_RECV(1000, ZBS_LOGTYPE_DEVICE) // it should be coordinator ZI_GOTO(ZIGBEE_LABEL_START_ROUTER) - // Device and Router code is common from now - // ZI_LABEL(ZIGBEE_LABEL_START_DEVICE) // Init as a router - // ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredDevice) - // ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) - // ZI_SEND(ZBS_AF_REGISTER_ALL) // Z_AF register for endpoint 01, profile 0x0104 Home Automation - // ZI_WAIT_RECV(1000, ZBR_AF_REGISTER) - // ZI_SEND(ZBS_STARTUPFROMAPP) // start router - // ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command - // ZI_WAIT_UNTIL_FUNC(0xFFFF, AREQ_STARTUPFROMAPP, &Z_ReceiveStateChange) // wait forever for async message that coordinator started - // ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo - // ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo) - // ZI_GOTO(ZIGBEE_LABEL_READY) ZI_LABEL(ZIGBEE_LABEL_FACT_RESET_DEVICE) // Factory reset for router ZI_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, kResetting) @@ -608,25 +596,12 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_WAIT_RECV(1000, ZBR_W_OK) ZI_GOTO(ZIGBEE_LABEL_FACT_RESET_ROUTER_DEVICE_POST) - // ZI_SEND(ZBS_W_ALL_PAN) // write universal PAN ID = 0xFFFF - // ZI_WAIT_RECV(1000, ZBR_W_OK) - // ZI_SEND(ZBS_W_ALL_CHANN) // write Allows all CHANNELS = 0x07FFF800, 11-26 - // ZI_WAIT_RECV(1000, ZBR_W_OK) - - // // Now mark the device as ready, writing 0x55 in memory slot 0x0F00 - // ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured - // ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite) - // ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured - // ZI_WAIT_RECV(1000, ZBR_WNV_OK) - - // ZI_GOTO(ZIGBEE_LABEL_START_ROUTER) - // ZI_GOTO(ZIGBEE_LABEL_START_DEVICE) - - + // Error: version of Z-Stack is not supported ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION) ZI_MQTT_STATE(ZIGBEE_STATUS_UNSUPPORTED_VERSION, kZNP12) ZI_GOTO(ZIGBEE_LABEL_ABORT) + // Abort state machine, general error ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort ZI_MQTT_STATE(ZIGBEE_STATUS_ABORT, kAbort) ZI_LOG(LOG_LEVEL_ERROR, kZigbeeAbort) diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index cae5441b7..20aecd0f9 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -52,7 +52,7 @@ int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) { ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state, device_associated); - if (device_associated > 0) { + if (device_associated > 0) { // If there are devices registered in CC2530, print the list uint idx = 16; ResponseAppend_P(PSTR(",\"AssocDevicesList\":[")); for (uint32_t i = 0; i < device_associated; i++) { @@ -87,18 +87,20 @@ int32_t Z_Reboot(int32_t res, class SBuffer &buf) { // print information about the reboot of device // 4180.02.02.00.02.06.03 // - static const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog"; - uint8_t reason = buf.get8(2); uint8_t transport_rev = buf.get8(3); uint8_t product_id = buf.get8(4); uint8_t major_rel = buf.get8(5); uint8_t minor_rel = buf.get8(6); uint8_t hw_rev = buf.get8(7); - char reason_str[12]; + const char *reason_str; - if (reason > 3) { reason = 3; } - GetTextIndexed(reason_str, sizeof(reason_str), reason, Z_RebootReason); + switch (reason) { + case 0: reason_str = PSTR("Power-up"); break; + case 1: reason_str = PSTR("External"); break; + case 2: reason_str = PSTR("Watchdog"); break; + default: reason_str = PSTR("Unknown"); break; + } Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" "\"Status\":%d,\"Message\":\"CC2530 booted\",\"RestartReason\":\"%s\"" @@ -201,7 +203,6 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) { return -1; } -const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" }; int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) { // Received ZDO_NODE_DESC_RSP Z_ShortAddress srcAddr = buf.get16(2); @@ -217,15 +218,22 @@ int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) { uint16_t maxOutTransferSize = buf.get16(17); uint8_t descriptorCapabilities = buf.get8(19); + if (0 == status) { uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device - if (deviceType > 3) { deviceType = 3; } + const char * deviceTypeStr; + switch (deviceType) { + case 0: deviceTypeStr = PSTR("Coordinator"); break; + case 1: deviceTypeStr = PSTR("Router"); break; + case 2: deviceTypeStr = PSTR("Device"); break; + default: deviceTypeStr = PSTR("Unknown"); break; + } bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0; Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" "\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"), - ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType], - complexDescriptorAvailable ? "true" : "false" + ZIGBEE_STATUS_NODE_DESC, deviceTypeStr, + complexDescriptorAvailable ? PSTR("true") : PSTR("false") ); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); @@ -282,15 +290,13 @@ int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) { Uint64toHex(ieeeAddr, hex, 64); // Ping response const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" + ",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""), nwkAddr, hex); if (friendlyName) { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\"" - ",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), nwkAddr, hex, friendlyName); - } else { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\"" - "}}"), nwkAddr, hex); + ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName); } + ResponseAppend_P(PSTR("\"}}")); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); @@ -398,9 +404,9 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { "\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\"" ",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"), ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr, - (capabilities & 0x04) ? "true" : "false", - (capabilities & 0x08) ? "true" : "false", - (capabilities & 0x40) ? "true" : "false" + (capabilities & 0x04) ? PSTR("true") : PSTR("false"), + (capabilities & 0x08) ? PSTR("true") : PSTR("false"), + (capabilities & 0x40) ? PSTR("true") : PSTR("false") ); // query the state of the bulb (for Alexa) uint32_t wait_ms = 2000; // wait for 2s @@ -444,18 +450,15 @@ int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) { uint8_t status = buf.get8(4); const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), nwkAddr); if (friendlyName) { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"" - ",\"" D_JSON_ZIGBEE_STATUS "\":%d" - ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), nwkAddr, friendlyName, status, getZigbeeStatusMessage(status).c_str()); - } else { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_STATUS "\":%d" - ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), nwkAddr, status, getZigbeeStatusMessage(status).c_str()); + ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName); } + ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d" + ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" + "}}"), status, getZigbeeStatusMessage(status).c_str()); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); @@ -470,18 +473,14 @@ int32_t Z_UnbindRsp(int32_t res, const class SBuffer &buf) { uint8_t status = buf.get8(4); const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); + + Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), nwkAddr); if (friendlyName) { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"" - ",\"" D_JSON_ZIGBEE_STATUS "\":%d" - ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), nwkAddr, friendlyName, status, getZigbeeStatusMessage(status).c_str()); - } else { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_STATUS "\":%d" - ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" - "}}"), nwkAddr, status, getZigbeeStatusMessage(status).c_str()); + ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName); } + ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" + "}}"), status, getZigbeeStatusMessage(status).c_str()); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); XdrvRulesProcess(); @@ -506,7 +505,6 @@ int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) { ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d" ",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\"" ",\"BindingsTotal\":%d" - //",\"BindingsStart\":%d" ",\"Bindings\":[" ), status, getZigbeeStatusMessage(status).c_str(), bind_total); @@ -631,7 +629,7 @@ int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t clu int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { uint16_t groupid = buf.get16(2); uint16_t clusterid = buf.get16(4); - Z_ShortAddress srcaddr = buf.get16(6); + uint16_t srcaddr = buf.get16(6); uint8_t srcendpoint = buf.get8(8); uint8_t dstendpoint = buf.get8(9); uint8_t wasbroadcast = buf.get8(10); diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 983c76cf3..40053c5a7 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -147,7 +147,7 @@ void ZigbeeInputLoop(void) // Initialize internal structures void ZigbeeInit(void) { - // Check if settings if Flash are set + // Check if settings in Flash are set if (0 == Settings.zb_channel) { AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Initializing Zigbee parameters from defaults")); Settings.zb_ext_panid = USE_ZIGBEE_EXTPANID; @@ -314,7 +314,7 @@ void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterI SBuffer buf(32+len); buf.add8(Z_SREQ | Z_AF); // 24 buf.add8(AF_DATA_REQUEST_EXT); // 02 - if (0x0000 == shortaddr) { // if no shortaddr we assume group address + if (BAD_SHORTADDR == shortaddr) { // if no shortaddr we assume group address buf.add8(Z_Addr_Group); // 01 buf.add64(groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded buf.add8(0xFF); // dest endpoint is not used for group addresses @@ -372,7 +372,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, } } - if ((0 == endpoint) && (shortaddr)) { + if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) { // endpoint is not specified, let's try to find it from shortAddr, unless it's a group address endpoint = zigbee_devices.findFirstEndpoint(shortaddr); //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); @@ -380,7 +380,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"), shortaddr, groupaddr, cluster, endpoint, cmd, param); - if ((0 == endpoint) && (shortaddr)) { // endpoint null is ok for group address + if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) { // endpoint null is ok for group address AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); return; } @@ -415,7 +415,7 @@ void CmndZbSend(void) { // params static char delim[] = ", "; // delimiters for parameters - uint16_t device = 0x0000; // 0x0000 is local, so considered invalid + uint16_t device = BAD_SHORTADDR; // 0x0000 is local, so considered invalid uint16_t groupaddr = 0x0000; // group address uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint uint16_t manuf = 0x0000; // Manuf Id in ZCL frame @@ -430,9 +430,9 @@ void CmndZbSend(void) { const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); if (nullptr != &val_device) { device = zigbee_devices.parseDeviceParam(val_device.as()); - if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } } - if (0x0000 == device) { // if not found, check if we have a group + if (BAD_SHORTADDR == device) { // if not found, check if we have a group const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group")); if (nullptr != &val_group) { groupaddr = strToUInt(val_group); @@ -571,8 +571,8 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind // params // static char delim[] = ", "; // delimiters for parameters - uint16_t srcDevice = 0xFFFF; // 0xFFFF is broadcast, so considered invalid - uint16_t dstDevice = 0xFFFF; // 0xFFFF is broadcast, so considered invalid + uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid + uint16_t dstDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid uint64_t dstLongAddr = 0; uint8_t endpoint = 0x00; // 0x00 is invalid for the src endpoint uint8_t toendpoint = 0x00; // 0x00 is invalid for the dst endpoint @@ -585,9 +585,8 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); if (nullptr != &val_device) { srcDevice = zigbee_devices.parseDeviceParam(val_device.as()); - if (0xFFFF == srcDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } } - if ((nullptr == &val_device) || (0x0000 == srcDevice)) { ResponseCmndChar_P(PSTR("Unknown source device")); return; } + if ((nullptr == &val_device) || (BAD_SHORTADDR == srcDevice)) { ResponseCmndChar_P(PSTR("Unknown source device")); return; } // check if IEEE address is known uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice); if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; } @@ -605,7 +604,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind const JsonVariant &dst_device = getCaseInsensitive(json, PSTR("ToDevice")); if (nullptr != &dst_device) { dstDevice = zigbee_devices.parseDeviceParam(dst_device.as()); - if (0xFFFF == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } if (0x0000 == dstDevice) { dstLongAddr = localIEEEAddr; } else { @@ -622,8 +621,8 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind if (nullptr != &to_group) { toGroup = strToUInt(to_group); } // make sure we don't have conflicting parameters - if (toGroup && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; } - if (!toGroup && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; } + if (&to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; } + if (!&to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; } SBuffer buf(34); buf.add8(Z_SREQ | Z_ZDO); @@ -670,8 +669,7 @@ void CmndZbUnbind(void) { void CmndZbBindState(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); - if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } SBuffer buf(10); buf.add8(Z_SREQ | Z_ZDO); // 25 @@ -695,8 +693,7 @@ void CmndZbProbe(void) { void CmndZbProbeOrPing(boolean probe) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); - if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } // everything is good, we can send the command Z_SendIEEEAddrReq(shortaddr); @@ -731,8 +728,7 @@ void CmndZbName(void) { // parse first part, uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered - if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } if (p == nullptr) { const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr); @@ -763,8 +759,7 @@ void CmndZbModelId(void) { // parse first part, uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered - if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } if (p == nullptr) { const char * modelId = zigbee_devices.getModelId(shortaddr); @@ -793,8 +788,7 @@ void CmndZbLight(void) { // parse first part, uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered - if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } if (p) { int8_t bulbtype = strtol(p, nullptr, 10); @@ -817,8 +811,7 @@ void CmndZbLight(void) { void CmndZbForget(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); - if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } // everything is good, we can send the command if (zigbee_devices.removeDevice(shortaddr)) { @@ -906,7 +899,7 @@ void CmndZbRead(void) { if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } // params - uint16_t device = 0xFFFF; // 0xFFFF is braodcast, so considered valid + uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid uint16_t groupaddr = 0x0000; // if 0x0000 ignore group adress uint16_t cluster = 0x0000; // default to general cluster uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint @@ -917,9 +910,9 @@ void CmndZbRead(void) { const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); if (nullptr != &val_device) { device = zigbee_devices.parseDeviceParam(val_device.as()); - if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } + if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } } - if (0x0000 == device) { // if not found, check if we have a group + if (BAD_SHORTADDR == device) { // if not found, check if we have a group const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group")); if (nullptr != &val_group) { groupaddr = strToUInt(val_group); @@ -960,9 +953,9 @@ void CmndZbRead(void) { if ((0 == endpoint) && (device)) { // try to compute the endpoint endpoint = zigbee_devices.findFirstEndpoint(device); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbRead: guessing endpoint 0x%02X"), endpoint); } - if (0x0000 == device) { + if (BAD_SHORTADDR == device) { endpoint = 0xFF; // endpoint not used for group addresses } @@ -1012,9 +1005,8 @@ void CmndZbStatus(void) { if (ZigbeeSerial) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); - if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } if (XdrvMailbox.payload > 0) { - if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } } String dump = zigbee_devices.dump(XdrvMailbox.index, shortaddr); From 950a4dc998b37cdb0ec04edda874a07f2902be44 Mon Sep 17 00:00:00 2001 From: Federico Leoni Date: Sun, 17 May 2020 13:42:14 -0300 Subject: [PATCH 21/21] HA Discovery: Oops! New DeviceName --- tasmota/xdrv_12_home_assistant.ino | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tasmota/xdrv_12_home_assistant.ino b/tasmota/xdrv_12_home_assistant.ino index 3e8663d24..e9d8bd009 100644 --- a/tasmota/xdrv_12_home_assistant.ino +++ b/tasmota/xdrv_12_home_assistant.ino @@ -233,7 +233,7 @@ void HAssAnnounceRelayLight(void) } else { if (Settings.flag.hass_discovery && (RelayX || (Light.device > 0) && (max_lights > 0)) && !err_flag ) { // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59) - char name[33 + 2]; // friendlyname(33) + " " + index + char name[TOPSZ]; // friendlyname(33) + " " + index char value_template[33]; char prefix[TOPSZ]; char *command_topic = stemp1; @@ -241,9 +241,9 @@ void HAssAnnounceRelayLight(void) char *availability_topic = stemp3; if (i > MAX_FRIENDLYNAMES) { - snprintf_P(name, sizeof(name), PSTR("%s %d"), SettingsText(SET_FRIENDLYNAME1), i-1); + snprintf_P(name, sizeof(name), PSTR("%s %s %d"), SettingsText(SET_DEVICENAME), SettingsText(SET_FRIENDLYNAME1), i-1); } else { - snprintf_P(name, sizeof(name), SettingsText(SET_FRIENDLYNAME1 + i-1)); + snprintf_P(name, sizeof(name), PSTR ("%s %s"), SettingsText(SET_DEVICENAME), SettingsText(SET_FRIENDLYNAME1 + i-1)); } GetPowerDevice(value_template, i, sizeof(value_template), Settings.flag.device_index_enable); // SetOption26 - Switch between POWER or POWER1 @@ -339,7 +339,7 @@ void HAssAnnouncerTriggers(uint8_t device, uint8_t present, uint8_t key, uint8_t snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/device_automation/%s/config"), unique_id); if (Settings.flag.hass_discovery && present) { // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59) - char name[33 + 6]; // friendlyname(33) + " " + "BTN" + " " + index + char name[TOPSZ]; // friendlyname(33) + " " + "BTN" + " " + index char value_template[33]; char prefix[TOPSZ]; char *state_topic = stemp1; @@ -391,7 +391,7 @@ void HAssAnnouncerBinSensors(uint8_t device, uint8_t present, uint8_t dual, uint if (Settings.flag.hass_discovery && present ) { // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59) if (!toggle || dual) { - char name[33 + 6]; // friendlyname(33) + " " + "BTN" + " " + index + char name[TOPSZ]; // friendlyname(33) + " " + "BTN" + " " + index char value_template[33]; char prefix[TOPSZ]; char *state_topic = stemp1; @@ -403,7 +403,7 @@ void HAssAnnouncerBinSensors(uint8_t device, uint8_t present, uint8_t dual, uint GetTopic_P(state_topic, STAT, mqtt_topic, jsoname); GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); - snprintf_P(name, sizeof(name), PSTR("%s Switch%d"), ModuleName().c_str(), device + 1); + snprintf_P(name, sizeof(name), PSTR("%s Switch%d"), SettingsText(SET_DEVICENAME), device + 1); Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic); if (!pir) { TryResponseAppend_P(HASS_DISCOVER_BIN_SWITCH, PSTR(D_RSLT_STATE), SettingsText(SET_STATE_TXT2), SettingsText(SET_STATE_TXT1)); @@ -553,13 +553,13 @@ void HAssAnnounceSensor(const char *sensorname, const char *subsensortype, const if (Settings.flag.hass_discovery) { // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59) - char name[33 + 42]; // friendlyname(33) + " " + sensorname(20?) + " " + sensortype(20?) + char name[TOPSZ]; // friendlyname(33) + " " + sensorname(20?) + " " + sensortype(20?) char prefix[TOPSZ]; char *state_topic = stemp1; char *availability_topic = stemp2; GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_SENSOR)); - snprintf_P(name, sizeof(name), PSTR("%s %s %s"), ModuleName().c_str(), sensorname, MultiSubName); + snprintf_P(name, sizeof(name), PSTR("%s %s %s"), SettingsText(SET_DEVICENAME), sensorname, MultiSubName); GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic); @@ -694,18 +694,18 @@ void HAssAnnounceDeviceInfoAndStatusSensor(void) if (Settings.flag.hass_discovery) { // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59) - char name[33 + 7]; // friendlyname(33) + " " + "status" + char name[TOPSZ]; // friendlyname(33) + " " + "status" char prefix[TOPSZ]; char *state_topic = stemp1; char *availability_topic = stemp2; - snprintf_P(name, sizeof(name), PSTR("%s status"), ModuleName().c_str()); + snprintf_P(name, sizeof(name), PSTR("%s status"), SettingsText(SET_DEVICENAME)); GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_HASS_STATE)); GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT); Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic); TryResponseAppend_P(HASS_DISCOVER_SENSOR_HASS_STATUS, state_topic); - TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO, unique_id, ESP_getChipId(), ModuleName().c_str(), + TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO, unique_id, ESP_getChipId(), SettingsText(SET_DEVICENAME), ModuleName().c_str(), my_version, my_image); TryResponseAppend_P(PSTR("}")); }