From b53dee396ecdde3ee687d426cb1a12ce0f894a9e Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 15:42:44 +0200 Subject: [PATCH 01/30] Lower minimum PWMFrequency to 40Hz --- tasmota/core_esp8266_wiring_pwm.cpp | 4 ++-- tasmota/tasmota.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp index 90d69b313..984ce92db 100644 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -45,8 +45,8 @@ extern void __analogWriteRange(uint32_t range) { extern void __analogWriteFreq(uint32_t freq) { - if (freq < 100) { - analogFreq = 100; + if (freq < 40) { // Arduino sets a minimum of 100Hz, waiting for them to change this one. + analogFreq = 40; } else if (freq > 60000) { analogFreq = 60000; } else { diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 77bbb6786..cca1af80f 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -106,7 +106,7 @@ const uint32_t PWM_RANGE = 1023; // 255..1023 needs to be devisible b //const uint16_t PWM_FREQ = 910; // 100..1000 Hz led refresh (iTead value) const uint16_t PWM_FREQ = 223; // 100..4000 Hz led refresh const uint16_t PWM_MAX = 4000; // [PWM_MAX] Maximum frequency - Default: 4000 -const uint16_t PWM_MIN = 100; // [PWM_MIN] Minimum frequency - Default: 100 +const uint16_t PWM_MIN = 40; // [PWM_MIN] Minimum frequency - Default: 40 // For Dimmers use double of your mains AC frequecy (100 for 50Hz and 120 for 60Hz) // For Controlling Servos use 50 and also set PWM_FREQ as 50 (DO NOT USE THESE VALUES FOR DIMMERS) From 0572644c7f0d19a402326e4ca05889f42d612108 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 8 May 2020 16:06:02 +0200 Subject: [PATCH 02/30] Change PWM Frequencies - Change default PWM Frequency to 977 Hz from 880 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz --- RELEASENOTES.md | 4 +++- tasmota/CHANGELOG.md | 3 +++ tasmota/tasmota.h | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d880bc05e..e86665a5d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -56,7 +56,8 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Breaking Change Device Groups multicast address and port (#8270) - Change PWM implementation to Arduino #7231 removing support for Core versions before 2.6.3 -- Change default PWM Frequency to 223 Hz instead of 880 Hz for less interrupt pressure +- Change default PWM Frequency to 977 Hz from 880 Hz +- Change minimum PWM Frequency from 100 Hz to 40 Hz - Change flash access removing support for any Core before 2.6.3 - Change HM-10 sensor type detection and add features (#7962) - Change light scheme 2,3,4 cycle time speed from 24,48,72,... seconds to 4,6,12,24,36,48,... seconds (#8034) @@ -100,4 +101,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add more accuracy to GPS NTP server (#8088) - Add support for analog anemometer by Matteo Albinola (#8283) - Add support for OpenTherm by Yuriy Sannikov (#8373) +- Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index cb730a8cb..ebfd0e3ae 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -5,8 +5,11 @@ - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt - Add support for analog anemometer by Matteo Albinola (#8283) - Add support for OpenTherm by Yuriy Sannikov (#8373) +- Add support for Thermostat control by arijav (#8212) - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) +- Change default PWM Frequency to 977 Hz from 223 Hz +- Change minimum PWM Frequency from 100 Hz to 40 Hz ### 8.2.0.5 20200425 diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index cca1af80f..ab152f9ec 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -104,7 +104,7 @@ const uint16_t WS2812_MAX_LEDS = 512; // Max number of LEDs const uint32_t PWM_RANGE = 1023; // 255..1023 needs to be devisible by 256 //const uint16_t PWM_FREQ = 1000; // 100..1000 Hz led refresh //const uint16_t PWM_FREQ = 910; // 100..1000 Hz led refresh (iTead value) -const uint16_t PWM_FREQ = 223; // 100..4000 Hz led refresh +const uint16_t PWM_FREQ = 977; // 100..4000 Hz led refresh const uint16_t PWM_MAX = 4000; // [PWM_MAX] Maximum frequency - Default: 4000 const uint16_t PWM_MIN = 40; // [PWM_MIN] Minimum frequency - Default: 40 // For Dimmers use double of your mains AC frequecy (100 for 50Hz and 120 for 60Hz) From f66a0ee561fa794119163aeceeb13660a77d4c4a Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 8 May 2020 17:17:09 +0200 Subject: [PATCH 03/30] Add Webcam commands --- tasmota/settings.h | 35 +++++++- tasmota/xdrv_81_webcam.ino | 176 ++++++++++++++++++++++++++----------- 2 files changed, 157 insertions(+), 54 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index 6b74cb269..3925aa30d 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -182,6 +182,35 @@ typedef union { }; } Timer; +typedef union { // Restricted by MISRA-C Rule 18.4 but so useful... + uint32_t data; + struct { + uint32_t stream : 1; + uint32_t mirror : 1; + uint32_t flip : 1; + uint32_t spare3 : 1; + uint32_t spare4 : 1; + uint32_t spare5 : 1; + uint32_t spare6 : 1; + uint32_t spare7 : 1; + uint32_t spare8 : 1; + uint32_t spare9 : 1; + uint32_t spare10 : 1; + uint32_t spare11 : 1; + uint32_t spare12 : 1; + uint32_t spare13 : 1; + uint32_t spare14 : 1; + uint32_t spare15 : 1; + uint32_t spare16 : 1; + uint32_t spare17 : 1; + uint32_t spare18 : 1; + uint32_t contrast : 3; + uint32_t brightness : 3; + uint32_t saturation : 3; + uint32_t resolution : 4; + }; +} WebCamCfg; + typedef union { uint16_t data; struct { @@ -365,9 +394,11 @@ struct { myio my_gp; // 3AC - 2 x 40 bytes (ESP32) mytmplt user_template; // 3FC - 2 x 37 bytes (ESP32) - uint8_t free_esp32_446[10]; // 446 + uint8_t free_esp32_446[6]; // 446 - uint8_t esp32_webcam_resolution; // 450 + WebCamCfg webcam_config; // 44C + + uint8_t free_esp32_450[1]; // 450 #endif // ESP8266 - ESP32 char serial_delimiter; // 451 diff --git a/tasmota/xdrv_81_webcam.ino b/tasmota/xdrv_81_webcam.ino index a317b1aaa..15c2dd4ba 100644 --- a/tasmota/xdrv_81_webcam.ino +++ b/tasmota/xdrv_81_webcam.ino @@ -230,11 +230,19 @@ uint32_t wc_setup(int32_t fsiz) { sensor_t * wc_s = esp_camera_sensor_get(); // initial sensors are flipped vertically and colors are a bit saturated +/* if (OV3660_PID == wc_s->id.PID) { wc_s->set_vflip(wc_s, 1); // flip it back wc_s->set_brightness(wc_s, 1); // up the brightness just a bit wc_s->set_saturation(wc_s, -2); // lower the saturation } +*/ + wc_s->set_vflip(wc_s, Settings.webcam_config.flip); + wc_s->set_hmirror(wc_s, Settings.webcam_config.mirror); + wc_s->set_brightness(wc_s, Settings.webcam_config.brightness -2); // up the brightness just a bit + wc_s->set_saturation(wc_s, Settings.webcam_config.saturation -2); // lower the saturation + wc_s->set_contrast(wc_s, Settings.webcam_config.contrast -2); // keep contrast + // drop down frame size for higher initial frame rate wc_s->set_framesize(wc_s, (framesize_t)fsiz); @@ -243,7 +251,6 @@ uint32_t wc_setup(int32_t fsiz) { wc_height = wc_fb->height; esp_camera_fb_return(wc_fb); - #ifdef USE_FACE_DETECT fd_init(); #endif @@ -461,11 +468,11 @@ WiFiClient client; void handleMjpeg(void) { AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Handle camserver")); - //if (!wc_stream_active) { + if (!wc_stream_active) { wc_stream_active = 1; client = CamServer->client(); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Create client")); - //} + } } #ifdef USE_FACE_DETECT @@ -613,11 +620,9 @@ void handleMjpeg_task(void) { bool jpeg_converted = false; if (!client.connected()) { - wc_stream_active = 0; AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Client fail")); - goto exit; + wc_stream_active = 0; } - if (1 == wc_stream_active) { client.flush(); client.setTimeout(3); @@ -626,15 +631,15 @@ void handleMjpeg_task(void) { "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" "\r\n"); wc_stream_active = 2; - } else { + } + if (2 == wc_stream_active) { wc_fb = esp_camera_fb_get(); if (!wc_fb) { - wc_stream_active = 0; AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Frame fail")); - goto exit; + wc_stream_active = 0; } - - + } + if (2 == wc_stream_active) { if (wc_fb->format != PIXFORMAT_JPEG) { jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len); if (!jpeg_converted){ @@ -673,13 +678,11 @@ void handleMjpeg_task(void) { if (jpeg_converted) { free(_jpg_buf); } esp_camera_fb_return(wc_fb); //AddLog_P2(WC_LOGLEVEL, PSTR("CAM: send frame")); - -exit: - if (!wc_stream_active) { - AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit")); - client.flush(); - client.stop(); - } + } + if (0 == wc_stream_active) { + AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit")); + client.flush(); + client.stop(); } } @@ -750,13 +753,6 @@ void detect_motion(void) { } } -void wc_show_stream(void) { - if (CamServer) { - WSContentSend_P(PSTR("

Webcam stream

"), - WiFi.localIP().toString().c_str()); - } -} - uint32_t wc_set_streamserver(uint32_t flag) { if (global_state.wifi_down) { return 0; } @@ -783,17 +779,30 @@ uint32_t wc_set_streamserver(uint32_t flag) { return 0; } -void WcStreamControl(uint32_t resolution) { - wc_set_streamserver(resolution); - /*if (0 == resolution) { - resolution=-1; - }*/ +void WcStreamControl() { + wc_set_streamserver(Settings.webcam_config.stream); + int resolution = (!Settings.webcam_config.stream) ? -1 : Settings.webcam_config.resolution; wc_setup(resolution); } +void WcShowStream(void) { + if (Settings.webcam_config.stream) { + if (!CamServer) { + WcStreamControl(); + delay(50); // Give the webcam webserver some time to prepare the stream + } + if (CamServer) { + WSContentSend_P(PSTR("

Webcam stream

"), + WiFi.localIP().toString().c_str()); + } + } +} + void wc_loop(void) { - if (CamServer) { CamServer->handleClient(); } - if (wc_stream_active) { handleMjpeg_task(); } + if (CamServer) { + CamServer->handleClient(); + if (wc_stream_active) { handleMjpeg_task(); } + } if (motion_detect) { detect_motion(); } #ifdef USE_FACE_DETECT if (face_detect_time) { detect_face(); } @@ -833,34 +842,103 @@ red led = gpio 33 */ void WcInit(void) { - if (Settings.esp32_webcam_resolution > 10) { - Settings.esp32_webcam_resolution = 0; + if (!Settings.webcam_config.data) { + Settings.webcam_config.stream = 1; + Settings.webcam_config.resolution = 5; + Settings.webcam_config.flip = 0; + Settings.webcam_config.mirror = 0; + Settings.webcam_config.saturation = 0; // -2 + Settings.webcam_config.brightness = 3; // 1 + Settings.webcam_config.contrast = 2; // 0 } } - /*********************************************************************************************\ * Commands \*********************************************************************************************/ -#define D_CMND_WEBCAM "Webcam" +#define D_PRFX_WEBCAM "WC" +#define D_CMND_WC_STREAM "Stream" +#define D_CMND_WC_RESOLUTION "Resolution" +#define D_CMND_WC_MIRROR "Mirror" +#define D_CMND_WC_FLIP "Flip" +#define D_CMND_WC_SATURATION "Saturation" +#define D_CMND_WC_BRIGHTNESS "Brightness" +#define D_CMND_WC_CONTRAST "Contrast" -const char kWCCommands[] PROGMEM = "|" // no prefix - D_CMND_WEBCAM +const char kWCCommands[] PROGMEM = D_PRFX_WEBCAM "|" // Prefix + "|" D_CMND_WC_STREAM "|" D_CMND_WC_RESOLUTION "|" D_CMND_WC_MIRROR "|" D_CMND_WC_FLIP "|" + D_CMND_WC_SATURATION "|" D_CMND_WC_BRIGHTNESS "|" D_CMND_WC_CONTRAST ; void (* const WCCommand[])(void) PROGMEM = { - &CmndWebcam, + &CmndWebcam, &CmndWebcamStream, &CmndWebcamResolution, &CmndWebcamMirror, &CmndWebcamFlip, + &CmndWebcamSaturation, &CmndWebcamBrightness, &CmndWebcamContrast }; void CmndWebcam(void) { - uint32_t flag = 0; - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { - Settings.esp32_webcam_resolution=XdrvMailbox.payload; - WcStreamControl(Settings.esp32_webcam_resolution); + Response_P(PSTR("{\"" D_PRFX_WEBCAM "\":{\"" D_CMND_WC_STREAM "\":%d,\"" D_CMND_WC_RESOLUTION "\":%d,\"" D_CMND_WC_MIRROR "\":%d,\"" + D_CMND_WC_FLIP "\":%d,\"" + D_CMND_WC_SATURATION "\":%d,\"" D_CMND_WC_BRIGHTNESS "\":%d,\"" D_CMND_WC_CONTRAST "\":%d}}"), + Settings.webcam_config.stream, Settings.webcam_config.resolution, Settings.webcam_config.mirror, + Settings.webcam_config.flip, + Settings.webcam_config.saturation -2, Settings.webcam_config.brightness -2, Settings.webcam_config.contrast -2); +} + +void CmndWebcamStream(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.webcam_config.stream = XdrvMailbox.payload; + if (!Settings.webcam_config.stream) { WcStreamControl(); } // Stop stream } - if (CamServer) { flag = 1; } - Response_P(PSTR("{\"" D_CMND_WEBCAM "\":{\"Streaming\":\"%s\"}"),GetStateText(flag)); + ResponseCmndStateText(Settings.webcam_config.stream); +} + +void CmndWebcamResolution(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { + Settings.webcam_config.resolution = XdrvMailbox.payload; + wc_set_options(0, Settings.webcam_config.resolution); + } + ResponseCmndNumber(Settings.webcam_config.resolution); +} + +void CmndWebcamMirror(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.webcam_config.mirror = XdrvMailbox.payload; + wc_set_options(3, Settings.webcam_config.mirror); + } + ResponseCmndStateText(Settings.webcam_config.mirror); +} + +void CmndWebcamFlip(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.webcam_config.flip = XdrvMailbox.payload; + wc_set_options(2, Settings.webcam_config.flip); + } + ResponseCmndStateText(Settings.webcam_config.flip); +} + +void CmndWebcamSaturation(void) { + if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) { + Settings.webcam_config.saturation = XdrvMailbox.payload +2; + wc_set_options(6, Settings.webcam_config.saturation -2); + } + ResponseCmndNumber(Settings.webcam_config.saturation -2); +} + +void CmndWebcamBrightness(void) { + if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) { + Settings.webcam_config.brightness = XdrvMailbox.payload +2; + wc_set_options(5, Settings.webcam_config.brightness -2); + } + ResponseCmndNumber(Settings.webcam_config.brightness -2); +} + +void CmndWebcamContrast(void) { + if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) { + Settings.webcam_config.contrast = XdrvMailbox.payload +2; + wc_set_options(4, Settings.webcam_config.contrast -2); + } + ResponseCmndNumber(Settings.webcam_config.contrast -2); } /*********************************************************************************************\ @@ -878,13 +956,7 @@ bool Xdrv81(uint8_t function) { wc_pic_setup(); break; case FUNC_WEB_ADD_MAIN_BUTTON: - if (Settings.esp32_webcam_resolution) { -//#ifndef USE_SCRIPT - WcStreamControl(Settings.esp32_webcam_resolution); - delay(50); // Give the webcam webserver some time to prepare the stream - wc_show_stream(); -//#endif - } + WcShowStream(); break; case FUNC_COMMAND: result = DecodeCommand(kWCCommands, WCCommand); From 3462c8d05dc541908af265e0008dd733e94427e0 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 17:49:29 +0200 Subject: [PATCH 04/30] Change PWM updated to the latest version of Arduino PR #7231 --- tasmota/CHANGELOG.md | 1 + tasmota/core_esp8266_waveform.cpp | 362 ++++++++++++++-------------- tasmota/core_esp8266_wiring_pwm.cpp | 30 +-- 3 files changed, 192 insertions(+), 201 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index ebfd0e3ae..5a35b9b6d 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -10,6 +10,7 @@ - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz +- Change PWM updated to the latest version of Arduino PR #7231 ### 8.2.0.5 20200425 diff --git a/tasmota/core_esp8266_waveform.cpp b/tasmota/core_esp8266_waveform.cpp index c39670686..deca23f54 100644 --- a/tasmota/core_esp8266_waveform.cpp +++ b/tasmota/core_esp8266_waveform.cpp @@ -55,21 +55,15 @@ extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32 // Maximum delay between IRQs #define MAXIRQUS (10000) -// Set/clear GPIO 0-15 by bitmask -#define SetGPIO(a) do { GPOS = a; } while (0) -#define ClearGPIO(a) do { GPOC = a; } while (0) - // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextServiceCycle; // ESP cycle timer when a transition required uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop - uint32_t timeHighCycles; // Currently running waveform period + uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles) uint32_t timeLowCycles; // - uint32_t desiredHighCycles; // Currently running waveform period - uint32_t desiredLowCycles; // - uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase - uint32_t gotoTimeLowCycles; // - uint32_t lastEdge; // + uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal + uint32_t desiredLowCycles; // + uint32_t lastEdge; // Cycle when this generator last changed } Waveform; class WVFState { @@ -82,7 +76,7 @@ public: uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation - int32_t waveformToChange = -1; + uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI uint32_t waveformNewHigh = 0; uint32_t waveformNewLow = 0; @@ -91,8 +85,8 @@ public: // Optimize the NMI inner loop by keeping track of the min and max GPIO that we // are generating. In the common case (1 PWM) these may be the same pin and // we can avoid looking at the other pins. - int startPin = 0; - int endPin = 0; + uint16_t startPin = 0; + uint16_t endPin = 0; }; static WVFState wvfState; @@ -107,7 +101,7 @@ static WVFState wvfState; static ICACHE_RAM_ATTR void timer1Interrupt(); static bool timerRunning = false; -static void initTimer() { +static __attribute__((noinline)) void initTimer() { if (!timerRunning) { timer1_disable(); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); @@ -138,21 +132,22 @@ static ICACHE_RAM_ATTR void forceTimerInterrupt() { constexpr int maxPWMs = 8; // PWM machine state -typedef struct { +typedef struct PWMState { uint32_t mask; // Bitmask of active pins uint32_t cnt; // How many entries uint32_t idx; // Where the state machine is along the list uint8_t pin[maxPWMs + 1]; uint32_t delta[maxPWMs + 1]; uint32_t nextServiceCycle; // Clock cycle for next step + struct PWMState *pwmUpdate; // Set by main code, cleared by ISR } PWMState; static PWMState pwmState; -static PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR -static uint32_t pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; - +static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; +// If there are no more scheduled activities, shut down Timer 1. +// Otherwise, do nothing. static ICACHE_RAM_ATTR void disableIdleTimer() { if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); @@ -162,62 +157,78 @@ static ICACHE_RAM_ATTR void disableIdleTimer() { } } +// Notify the NMI that a new PWM state is available through the mailbox. +// Wait for mailbox to be emptied (either busy or delay() as needed) +static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) { + p->pwmUpdate = nullptr; + pwmState.pwmUpdate = p; + MEMBARRIER(); + forceTimerInterrupt(); + while (pwmState.pwmUpdate) { + if (idle) { + delay(0); + } + MEMBARRIER(); + } +} + +static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range); // Called when analogWriteFreq() changed to update the PWM total period -void _setPWMPeriodCC(uint32_t cc) { - if (cc == pwmPeriod) { - return; +void _setPWMFreq(uint32_t freq) { + // Convert frequency into clock cycles + uint32_t cc = microsecondsToClockCycles(1000000UL) / freq; + + // Simple static adjustment to bring period closer to requested due to overhead +#if F_CPU == 80000000 + cc -= microsecondsToClockCycles(2); +#else + cc -= microsecondsToClockCycles(1); +#endif + + if (cc == _pwmPeriod) { + return; // No change } + + _pwmPeriod = cc; + if (pwmState.cnt) { - // Adjust any running ones to the best of our abilities by scaling them - // Used FP math for speed and code size - uint64_t oldCC64p0 = ((uint64_t)pwmPeriod); - uint64_t newCC64p16 = ((uint64_t)cc) << 16; - uint64_t ratio64p16 = (newCC64p16 / oldCC64p0); PWMState p; // The working copy since we can't edit the one in use - p = pwmState; - uint32_t ttl = 0; - for (uint32_t i = 0; i < p.cnt; i++) { - uint64_t val64p16 = ((uint64_t)p.delta[i]) << 16; - uint64_t newVal64p32 = val64p16 * ratio64p16; - p.delta[i] = newVal64p32 >> 32; - ttl += p.delta[i]; + p.cnt = 0; + for (uint32_t i = 0; i < pwmState.cnt; i++) { + auto pin = pwmState.pin[i]; + _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); } - p.delta[p.cnt] = cc - ttl; // Final cleanup exactly cc total cycles // Update and wait for mailbox to be emptied - pwmUpdate = &p; - MEMBARRIER(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - // No mem barrier. The external function call guarantees it's re-read - } + initTimer(); + _notifyPWM(&p, true); + disableIdleTimer(); } - pwmPeriod = cc; } // Helper routine to remove an entry from the state machine -static ICACHE_RAM_ATTR void _removePWMEntry(int pin, PWMState *p) { - uint32_t i; - - // Find the pin to pull out... - for (i = 0; p->pin[i] != pin; i++) { /* no-op */ } - auto delta = p->delta[i]; - - // Add the removed previous pin delta to preserve absolute position - p->delta[i+1] += delta; - - // Move everything back one - for (i++; i <= p->cnt; i++) { - p->pin[i-1] = p->pin[i]; - p->delta[i-1] = p->delta[i]; +// and clean up any marked-off entries +static void _cleanAndRemovePWM(PWMState *p, int pin) { + uint32_t leftover = 0; + uint32_t in, out; + for (in = 0, out = 0; in < p->cnt; in++) { + if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) { + p->pin[out] = p->pin[in]; + p->delta[out] = p->delta[in] + leftover; + leftover = 0; + out++; + } else { + leftover += p->delta[in]; + p->mask &= ~(1<pin[in]); + } } - // Remove the pin from the active list - p->mask &= ~(1<cnt--; + p->cnt = out; + // Final pin is never used: p->pin[out] = 0xff; + p->delta[out] = p->delta[in] + leftover; } -// Called by analogWrite(0/100%) to disable PWM on a specific pin + +// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) ICACHE_RAM_ATTR bool _stopPWM(int pin) { if (!((1<= _pwmPeriod) { + _stopPWM(pin); + digitalWrite(pin, HIGH); + return; } - // And add it to the list, in order - if (p.cnt >= maxPWMs) { - return false; // No space left - } else if (p.cnt == 0) { + + if (p.cnt == 0) { // Starting up from scratch, special case 1st element and PWM period p.pin[0] = pin; p.delta[0] = cc; - p.pin[1] = 0xff; - p.delta[1] = pwmPeriod - cc; - p.cnt = 1; - p.mask = 1<= (int)i; j--) { + p.pin[j + 1] = p.pin[j]; + p.delta[j + 1] = p.delta[j]; + } int off = cc - ttl; // The delta from the last edge to the one we're inserting p.pin[i] = pin; p.delta[i] = off; // Add the delta to this new pin p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant - p.cnt++; - p.mask |= 1<= maxPWMs) { + return false; // No space left } + _addPWMtoList(p, pin, val, range); + // Set mailbox and wait for ISR to copy it over - pwmUpdate = &p; - MEMBARRIER(); initTimer(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - } + _notifyPWM(&p, true); + disableIdleTimer(); return true; } @@ -311,22 +343,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time uint32_t mask = 1<= 0) { + // Make sure no waveform changes are waiting to be applied + while (wvfState.waveformToChange) { delay(0); // Wait for waveform to update // No mem barrier here, the call to a global function implies global state updated } + wvfState.waveformNewHigh = timeHighCycles; + wvfState.waveformNewLow = timeLowCycles; + MEMBARRIER(); + wvfState.waveformToChange = mask; + // The waveform will be updated some time in the future on the next period for the signal } else { // if (!(wvfState.waveformEnabled & mask)) { wave->timeHighCycles = timeHighCycles; + wave->desiredHighCycles = timeHighCycles; wave->timeLowCycles = timeLowCycles; - wave->desiredHighCycles = wave->timeHighCycles; - wave->desiredLowCycles = wave->timeLowCycles; + wave->desiredLowCycles = timeLowCycles; wave->lastEdge = 0; - wave->gotoTimeHighCycles = wave->timeHighCycles; - wave->gotoTimeLowCycles = wave->timeLowCycles; // Actually set the pin high or low in the IRQ service to guarantee times wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); wvfState.waveformToEnable |= mask; MEMBARRIER(); @@ -355,10 +387,10 @@ void setTimer1Callback(uint32_t (*fn)()) { // Speed critical bits #pragma GCC optimize ("O2") + // Normally would not want two copies like this, but due to different // optimization levels the inline attribute gets lost if we try the // other version. - static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { uint32_t ccount; __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); @@ -380,8 +412,13 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - if (wvfState.waveformEnabled & (1UL << pin)) { - wvfState.waveformToDisable = 1UL << pin; + uint32_t mask = 1<> (turbo ? 0 : 1)) #endif -#define ENABLE_ADJUST // Adjust takes 36 bytes -#define ENABLE_FEEDBACK // Feedback costs 68 bytes -#define ENABLE_PWM // PWM takes 160 bytes - -#ifndef ENABLE_ADJUST - #undef adjust - #define adjust(x) (x) -#endif - static ICACHE_RAM_ATTR void timer1Interrupt() { // Flag if the core is at 160 MHz, for use by adjust() @@ -435,19 +463,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); -#ifdef ENABLE_PWM - } else if (!pwmState.cnt && pwmUpdate) { + } else if (!pwmState.cnt && pwmState.pwmUpdate) { // Start up the PWM generator by copying from the mailbox pwmState.cnt = 1; pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! // No need for mem barrier here. Global must be written by IRQ exit -#endif - } else if (wvfState.waveformToChange >= 0) { - wvfState.waveform[wvfState.waveformToChange].gotoTimeHighCycles = wvfState.waveformNewHigh; - wvfState.waveform[wvfState.waveformToChange].gotoTimeLowCycles = wvfState.waveformNewLow; - wvfState.waveformToChange = -1; - // No need for memory barrier here. The global has to be written before exit the ISR. } bool done = false; @@ -455,35 +476,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { nextEventCycles = microsecondsToClockCycles(MAXIRQUS); -#ifdef ENABLE_PWM // PWM state machine implementation if (pwmState.cnt) { - uint32_t now = GetCycleCountIRQ(); - int32_t cyclesToGo = pwmState.nextServiceCycle - now; + int32_t cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); if (cyclesToGo < 0) { if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new - if (pwmUpdate) { - // Do the memory copy from temp to global and clear mailbox - pwmState = *(PWMState*)pwmUpdate; - pwmUpdate = nullptr; - } - GPOS = pwmState.mask; // Set all active pins high - // GPIO16 isn't the same as the others - if (pwmState.mask & (1<<16)) { - GP16O = 1; - } - pwmState.idx = 0; + if (pwmState.pwmUpdate) { + // Do the memory copy from temp to global and clear mailbox + pwmState = *(PWMState*)pwmState.pwmUpdate; + } + GPOS = pwmState.mask; // Set all active pins high + if (pwmState.mask & (1<<16)) { + GP16O = 1; + } + pwmState.idx = 0; } else { - do { - // Drop the pin at this edge - GPOC = 1<expiryCycle - now; if (expiryToGo < 0) { // Done, remove! - wvfState.waveformEnabled &= ~mask; if (i == 16) { GP16O = 0; - } else { - ClearGPIO(mask); - } + } + GPOC = mask; + wvfState.waveformEnabled &= ~mask; continue; } } @@ -528,39 +544,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.waveformState ^= mask; if (wvfState.waveformState & mask) { if (i == 16) { - GP16O = 1; // GPIO16 write slow as it's RMW - } else { - SetGPIO(mask); + GP16O = 1; } - if (wave->gotoTimeHighCycles) { + GPOS = mask; + + if (wvfState.waveformToChange & mask) { // Copy over next full-cycle timings - wave->timeHighCycles = wave->gotoTimeHighCycles; - wave->desiredHighCycles = wave->gotoTimeHighCycles; - wave->timeLowCycles = wave->gotoTimeLowCycles; - wave->desiredLowCycles = wave->gotoTimeLowCycles; - wave->gotoTimeHighCycles = 0; - } else { -#ifdef ENABLE_FEEDBACK - if (wave->lastEdge) { - desired = wave->desiredLowCycles; - timeToUpdate = &wave->timeLowCycles; - } + wave->timeHighCycles = wvfState.waveformNewHigh; + wave->desiredHighCycles = wvfState.waveformNewHigh; + wave->timeLowCycles = wvfState.waveformNewLow; + wave->desiredLowCycles = wvfState.waveformNewLow; + wave->lastEdge = 0; + wvfState.waveformToChange = 0; + } + if (wave->lastEdge) { + desired = wave->desiredLowCycles; + timeToUpdate = &wave->timeLowCycles; } -#endif nextEdgeCycles = wave->timeHighCycles; } else { if (i == 16) { - GP16O = 0; // GPIO16 write slow as it's RMW - } else { - ClearGPIO(mask); + GP16O = 0; } -#ifdef ENABLE_FEEDBACK + GPOC = mask; desired = wave->desiredHighCycles; timeToUpdate = &wave->timeHighCycles; -#endif nextEdgeCycles = wave->timeLowCycles; } -#ifdef ENABLE_FEEDBACK if (desired) { desired = adjust(desired); int32_t err = desired - (now - wave->lastEdge); @@ -569,7 +579,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { *timeToUpdate += err; } } -#endif nextEdgeCycles = adjust(nextEdgeCycles); wave->nextServiceCycle = now + nextEdgeCycles; nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles); @@ -599,7 +608,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Do it here instead of global function to save time and because we know it's edge-IRQ T1L = nextEventCycles >> (turbo ? 1 : 0); - TEIE |= TEIE1; // Edge int enable } }; diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp index 984ce92db..651e3bb11 100644 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -33,9 +33,7 @@ extern void _setPWMPeriodCC(uint32_t cc); extern bool _stopPWM(int pin); extern bool _setPWM(int pin, uint32_t cc); -static uint32_t analogMap = 0; static int32_t analogScale = PWMRANGE; -static uint16_t analogFreq = 1000; extern void __analogWriteRange(uint32_t range) { if (range > 0) { @@ -43,17 +41,15 @@ extern void __analogWriteRange(uint32_t range) { } } - extern void __analogWriteFreq(uint32_t freq) { - if (freq < 40) { // Arduino sets a minimum of 100Hz, waiting for them to change this one. - analogFreq = 40; + if (freq < 40) { + freq = 40; } else if (freq > 60000) { - analogFreq = 60000; + freq = 60000; } else { - analogFreq = freq; + freq = freq; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); + _setPWMFreq(freq); } extern void __analogWrite(uint8_t pin, int val) { @@ -61,28 +57,14 @@ extern void __analogWrite(uint8_t pin, int val) { return; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); if (val < 0) { val = 0; } else if (val > analogScale) { val = analogScale; } - analogMap &= ~(1 << pin); - uint32_t high = (analogPeriod * val) / analogScale; - uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); - if (low == 0) { - _stopPWM(pin); - digitalWrite(pin, HIGH); - } else if (high == 0) { - _stopPWM(pin); - digitalWrite(pin, LOW); - } else { - _setPWM(pin, high); - analogMap |= (1 << pin); - } + _setPWM(pin, val, analogScale); } extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); From 87a1cd0ea0b0682a600cdb87d554f37a6d59cb06 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 17:52:24 +0200 Subject: [PATCH 05/30] Change PWM updated to the latest version of Arduino PR #7231 --- tasmota/CHANGELOG.md | 1 + tasmota/core_esp8266_waveform.cpp | 366 ++++++++++++------------ tasmota/core_esp8266_wiring_digital.cpp | 4 +- tasmota/core_esp8266_wiring_pwm.cpp | 34 +-- 4 files changed, 198 insertions(+), 207 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index ebfd0e3ae..5a35b9b6d 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -10,6 +10,7 @@ - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz +- Change PWM updated to the latest version of Arduino PR #7231 ### 8.2.0.5 20200425 diff --git a/tasmota/core_esp8266_waveform.cpp b/tasmota/core_esp8266_waveform.cpp index c39670686..1ff0a3687 100644 --- a/tasmota/core_esp8266_waveform.cpp +++ b/tasmota/core_esp8266_waveform.cpp @@ -47,29 +47,23 @@ extern "C" { // Internal-only calls, not for applications -extern void _setPWMPeriodCC(uint32_t cc); +extern void _setPWMFreq(uint32_t freq); extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t cc); +extern bool _setPWM(int pin, uint32_t val, uint32_t range); extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); // Maximum delay between IRQs #define MAXIRQUS (10000) -// Set/clear GPIO 0-15 by bitmask -#define SetGPIO(a) do { GPOS = a; } while (0) -#define ClearGPIO(a) do { GPOC = a; } while (0) - // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextServiceCycle; // ESP cycle timer when a transition required uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop - uint32_t timeHighCycles; // Currently running waveform period + uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles) uint32_t timeLowCycles; // - uint32_t desiredHighCycles; // Currently running waveform period - uint32_t desiredLowCycles; // - uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase - uint32_t gotoTimeLowCycles; // - uint32_t lastEdge; // + uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal + uint32_t desiredLowCycles; // + uint32_t lastEdge; // Cycle when this generator last changed } Waveform; class WVFState { @@ -82,7 +76,7 @@ public: uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation - int32_t waveformToChange = -1; + uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI uint32_t waveformNewHigh = 0; uint32_t waveformNewLow = 0; @@ -91,8 +85,8 @@ public: // Optimize the NMI inner loop by keeping track of the min and max GPIO that we // are generating. In the common case (1 PWM) these may be the same pin and // we can avoid looking at the other pins. - int startPin = 0; - int endPin = 0; + uint16_t startPin = 0; + uint16_t endPin = 0; }; static WVFState wvfState; @@ -107,7 +101,7 @@ static WVFState wvfState; static ICACHE_RAM_ATTR void timer1Interrupt(); static bool timerRunning = false; -static void initTimer() { +static __attribute__((noinline)) void initTimer() { if (!timerRunning) { timer1_disable(); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); @@ -138,21 +132,22 @@ static ICACHE_RAM_ATTR void forceTimerInterrupt() { constexpr int maxPWMs = 8; // PWM machine state -typedef struct { +typedef struct PWMState { uint32_t mask; // Bitmask of active pins uint32_t cnt; // How many entries uint32_t idx; // Where the state machine is along the list uint8_t pin[maxPWMs + 1]; uint32_t delta[maxPWMs + 1]; uint32_t nextServiceCycle; // Clock cycle for next step + struct PWMState *pwmUpdate; // Set by main code, cleared by ISR } PWMState; static PWMState pwmState; -static PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR -static uint32_t pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; - +static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; +// If there are no more scheduled activities, shut down Timer 1. +// Otherwise, do nothing. static ICACHE_RAM_ATTR void disableIdleTimer() { if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); @@ -162,62 +157,78 @@ static ICACHE_RAM_ATTR void disableIdleTimer() { } } +// Notify the NMI that a new PWM state is available through the mailbox. +// Wait for mailbox to be emptied (either busy or delay() as needed) +static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) { + p->pwmUpdate = nullptr; + pwmState.pwmUpdate = p; + MEMBARRIER(); + forceTimerInterrupt(); + while (pwmState.pwmUpdate) { + if (idle) { + delay(0); + } + MEMBARRIER(); + } +} + +static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range); // Called when analogWriteFreq() changed to update the PWM total period -void _setPWMPeriodCC(uint32_t cc) { - if (cc == pwmPeriod) { - return; +void _setPWMFreq(uint32_t freq) { + // Convert frequency into clock cycles + uint32_t cc = microsecondsToClockCycles(1000000UL) / freq; + + // Simple static adjustment to bring period closer to requested due to overhead +#if F_CPU == 80000000 + cc -= microsecondsToClockCycles(2); +#else + cc -= microsecondsToClockCycles(1); +#endif + + if (cc == _pwmPeriod) { + return; // No change } + + _pwmPeriod = cc; + if (pwmState.cnt) { - // Adjust any running ones to the best of our abilities by scaling them - // Used FP math for speed and code size - uint64_t oldCC64p0 = ((uint64_t)pwmPeriod); - uint64_t newCC64p16 = ((uint64_t)cc) << 16; - uint64_t ratio64p16 = (newCC64p16 / oldCC64p0); PWMState p; // The working copy since we can't edit the one in use - p = pwmState; - uint32_t ttl = 0; - for (uint32_t i = 0; i < p.cnt; i++) { - uint64_t val64p16 = ((uint64_t)p.delta[i]) << 16; - uint64_t newVal64p32 = val64p16 * ratio64p16; - p.delta[i] = newVal64p32 >> 32; - ttl += p.delta[i]; + p.cnt = 0; + for (uint32_t i = 0; i < pwmState.cnt; i++) { + auto pin = pwmState.pin[i]; + _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); } - p.delta[p.cnt] = cc - ttl; // Final cleanup exactly cc total cycles // Update and wait for mailbox to be emptied - pwmUpdate = &p; - MEMBARRIER(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - // No mem barrier. The external function call guarantees it's re-read - } + initTimer(); + _notifyPWM(&p, true); + disableIdleTimer(); } - pwmPeriod = cc; } // Helper routine to remove an entry from the state machine -static ICACHE_RAM_ATTR void _removePWMEntry(int pin, PWMState *p) { - uint32_t i; - - // Find the pin to pull out... - for (i = 0; p->pin[i] != pin; i++) { /* no-op */ } - auto delta = p->delta[i]; - - // Add the removed previous pin delta to preserve absolute position - p->delta[i+1] += delta; - - // Move everything back one - for (i++; i <= p->cnt; i++) { - p->pin[i-1] = p->pin[i]; - p->delta[i-1] = p->delta[i]; +// and clean up any marked-off entries +static void _cleanAndRemovePWM(PWMState *p, int pin) { + uint32_t leftover = 0; + uint32_t in, out; + for (in = 0, out = 0; in < p->cnt; in++) { + if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) { + p->pin[out] = p->pin[in]; + p->delta[out] = p->delta[in] + leftover; + leftover = 0; + out++; + } else { + leftover += p->delta[in]; + p->mask &= ~(1<pin[in]); + } } - // Remove the pin from the active list - p->mask &= ~(1<cnt--; + p->cnt = out; + // Final pin is never used: p->pin[out] = 0xff; + p->delta[out] = p->delta[in] + leftover; } -// Called by analogWrite(0/100%) to disable PWM on a specific pin + +// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) ICACHE_RAM_ATTR bool _stopPWM(int pin) { if (!((1<= _pwmPeriod) { + _stopPWM(pin); + digitalWrite(pin, HIGH); + return; } - // And add it to the list, in order - if (p.cnt >= maxPWMs) { - return false; // No space left - } else if (p.cnt == 0) { + + if (p.cnt == 0) { // Starting up from scratch, special case 1st element and PWM period p.pin[0] = pin; p.delta[0] = cc; - p.pin[1] = 0xff; - p.delta[1] = pwmPeriod - cc; - p.cnt = 1; - p.mask = 1<= (int)i; j--) { + p.pin[j + 1] = p.pin[j]; + p.delta[j + 1] = p.delta[j]; + } int off = cc - ttl; // The delta from the last edge to the one we're inserting p.pin[i] = pin; p.delta[i] = off; // Add the delta to this new pin p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant - p.cnt++; - p.mask |= 1<= maxPWMs) { + return false; // No space left } + _addPWMtoList(p, pin, val, range); + // Set mailbox and wait for ISR to copy it over - pwmUpdate = &p; - MEMBARRIER(); initTimer(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - } + _notifyPWM(&p, true); + disableIdleTimer(); return true; } @@ -311,22 +343,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time uint32_t mask = 1<= 0) { + // Make sure no waveform changes are waiting to be applied + while (wvfState.waveformToChange) { delay(0); // Wait for waveform to update // No mem barrier here, the call to a global function implies global state updated } + wvfState.waveformNewHigh = timeHighCycles; + wvfState.waveformNewLow = timeLowCycles; + MEMBARRIER(); + wvfState.waveformToChange = mask; + // The waveform will be updated some time in the future on the next period for the signal } else { // if (!(wvfState.waveformEnabled & mask)) { wave->timeHighCycles = timeHighCycles; + wave->desiredHighCycles = timeHighCycles; wave->timeLowCycles = timeLowCycles; - wave->desiredHighCycles = wave->timeHighCycles; - wave->desiredLowCycles = wave->timeLowCycles; + wave->desiredLowCycles = timeLowCycles; wave->lastEdge = 0; - wave->gotoTimeHighCycles = wave->timeHighCycles; - wave->gotoTimeLowCycles = wave->timeLowCycles; // Actually set the pin high or low in the IRQ service to guarantee times wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); wvfState.waveformToEnable |= mask; MEMBARRIER(); @@ -355,10 +387,10 @@ void setTimer1Callback(uint32_t (*fn)()) { // Speed critical bits #pragma GCC optimize ("O2") + // Normally would not want two copies like this, but due to different // optimization levels the inline attribute gets lost if we try the // other version. - static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { uint32_t ccount; __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); @@ -380,8 +412,13 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - if (wvfState.waveformEnabled & (1UL << pin)) { - wvfState.waveformToDisable = 1UL << pin; + uint32_t mask = 1<> (turbo ? 0 : 1)) #endif -#define ENABLE_ADJUST // Adjust takes 36 bytes -#define ENABLE_FEEDBACK // Feedback costs 68 bytes -#define ENABLE_PWM // PWM takes 160 bytes - -#ifndef ENABLE_ADJUST - #undef adjust - #define adjust(x) (x) -#endif - static ICACHE_RAM_ATTR void timer1Interrupt() { // Flag if the core is at 160 MHz, for use by adjust() @@ -435,19 +463,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); -#ifdef ENABLE_PWM - } else if (!pwmState.cnt && pwmUpdate) { + } else if (!pwmState.cnt && pwmState.pwmUpdate) { // Start up the PWM generator by copying from the mailbox pwmState.cnt = 1; pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! // No need for mem barrier here. Global must be written by IRQ exit -#endif - } else if (wvfState.waveformToChange >= 0) { - wvfState.waveform[wvfState.waveformToChange].gotoTimeHighCycles = wvfState.waveformNewHigh; - wvfState.waveform[wvfState.waveformToChange].gotoTimeLowCycles = wvfState.waveformNewLow; - wvfState.waveformToChange = -1; - // No need for memory barrier here. The global has to be written before exit the ISR. } bool done = false; @@ -455,35 +476,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { nextEventCycles = microsecondsToClockCycles(MAXIRQUS); -#ifdef ENABLE_PWM // PWM state machine implementation if (pwmState.cnt) { - uint32_t now = GetCycleCountIRQ(); - int32_t cyclesToGo = pwmState.nextServiceCycle - now; + int32_t cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); if (cyclesToGo < 0) { if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new - if (pwmUpdate) { - // Do the memory copy from temp to global and clear mailbox - pwmState = *(PWMState*)pwmUpdate; - pwmUpdate = nullptr; - } - GPOS = pwmState.mask; // Set all active pins high - // GPIO16 isn't the same as the others - if (pwmState.mask & (1<<16)) { - GP16O = 1; - } - pwmState.idx = 0; + if (pwmState.pwmUpdate) { + // Do the memory copy from temp to global and clear mailbox + pwmState = *(PWMState*)pwmState.pwmUpdate; + } + GPOS = pwmState.mask; // Set all active pins high + if (pwmState.mask & (1<<16)) { + GP16O = 1; + } + pwmState.idx = 0; } else { - do { - // Drop the pin at this edge - GPOC = 1<expiryCycle - now; if (expiryToGo < 0) { // Done, remove! - wvfState.waveformEnabled &= ~mask; if (i == 16) { GP16O = 0; - } else { - ClearGPIO(mask); - } + } + GPOC = mask; + wvfState.waveformEnabled &= ~mask; continue; } } @@ -528,39 +544,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.waveformState ^= mask; if (wvfState.waveformState & mask) { if (i == 16) { - GP16O = 1; // GPIO16 write slow as it's RMW - } else { - SetGPIO(mask); + GP16O = 1; } - if (wave->gotoTimeHighCycles) { + GPOS = mask; + + if (wvfState.waveformToChange & mask) { // Copy over next full-cycle timings - wave->timeHighCycles = wave->gotoTimeHighCycles; - wave->desiredHighCycles = wave->gotoTimeHighCycles; - wave->timeLowCycles = wave->gotoTimeLowCycles; - wave->desiredLowCycles = wave->gotoTimeLowCycles; - wave->gotoTimeHighCycles = 0; - } else { -#ifdef ENABLE_FEEDBACK - if (wave->lastEdge) { - desired = wave->desiredLowCycles; - timeToUpdate = &wave->timeLowCycles; - } + wave->timeHighCycles = wvfState.waveformNewHigh; + wave->desiredHighCycles = wvfState.waveformNewHigh; + wave->timeLowCycles = wvfState.waveformNewLow; + wave->desiredLowCycles = wvfState.waveformNewLow; + wave->lastEdge = 0; + wvfState.waveformToChange = 0; + } + if (wave->lastEdge) { + desired = wave->desiredLowCycles; + timeToUpdate = &wave->timeLowCycles; } -#endif nextEdgeCycles = wave->timeHighCycles; } else { if (i == 16) { - GP16O = 0; // GPIO16 write slow as it's RMW - } else { - ClearGPIO(mask); + GP16O = 0; } -#ifdef ENABLE_FEEDBACK + GPOC = mask; desired = wave->desiredHighCycles; timeToUpdate = &wave->timeHighCycles; -#endif nextEdgeCycles = wave->timeLowCycles; } -#ifdef ENABLE_FEEDBACK if (desired) { desired = adjust(desired); int32_t err = desired - (now - wave->lastEdge); @@ -569,7 +579,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { *timeToUpdate += err; } } -#endif nextEdgeCycles = adjust(nextEdgeCycles); wave->nextServiceCycle = now + nextEdgeCycles; nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles); @@ -599,7 +608,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Do it here instead of global function to save time and because we know it's edge-IRQ T1L = nextEventCycles >> (turbo ? 1 : 0); - TEIE |= TEIE1; // Edge int enable } }; diff --git a/tasmota/core_esp8266_wiring_digital.cpp b/tasmota/core_esp8266_wiring_digital.cpp index d3a8fa737..199fbabd8 100644 --- a/tasmota/core_esp8266_wiring_digital.cpp +++ b/tasmota/core_esp8266_wiring_digital.cpp @@ -34,9 +34,9 @@ extern "C" { // Internal-only calls, not for applications -extern void _setPWMPeriodCC(uint32_t cc); +extern void _setPWMFreq(uint32_t freq); extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t cc); +extern bool _setPWM(int pin, uint32_t val, uint32_t range); extern void resetPins(); volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 }; diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp index 984ce92db..5189fa8f5 100644 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -29,13 +29,11 @@ extern "C" { // Internal-only calls, not for applications -extern void _setPWMPeriodCC(uint32_t cc); +extern void _setPWMFreq(uint32_t freq); extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t cc); +extern bool _setPWM(int pin, uint32_t val, uint32_t range); -static uint32_t analogMap = 0; static int32_t analogScale = PWMRANGE; -static uint16_t analogFreq = 1000; extern void __analogWriteRange(uint32_t range) { if (range > 0) { @@ -43,17 +41,15 @@ extern void __analogWriteRange(uint32_t range) { } } - extern void __analogWriteFreq(uint32_t freq) { - if (freq < 40) { // Arduino sets a minimum of 100Hz, waiting for them to change this one. - analogFreq = 40; + if (freq < 40) { + freq = 40; } else if (freq > 60000) { - analogFreq = 60000; + freq = 60000; } else { - analogFreq = freq; + freq = freq; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); + _setPWMFreq(freq); } extern void __analogWrite(uint8_t pin, int val) { @@ -61,28 +57,14 @@ extern void __analogWrite(uint8_t pin, int val) { return; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); if (val < 0) { val = 0; } else if (val > analogScale) { val = analogScale; } - analogMap &= ~(1 << pin); - uint32_t high = (analogPeriod * val) / analogScale; - uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); - if (low == 0) { - _stopPWM(pin); - digitalWrite(pin, HIGH); - } else if (high == 0) { - _stopPWM(pin); - digitalWrite(pin, LOW); - } else { - _setPWM(pin, high); - analogMap |= (1 << pin); - } + _setPWM(pin, val, analogScale); } extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); From a2b05399a3c5ad104304b1c357116a7fc1ee9fea Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 19:33:20 +0200 Subject: [PATCH 06/30] Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules --- .../generator/generator.c | 165 +++++ .../generator/remapping.xlsx | Bin 0 -> 12338 bytes .../library.properties.txt | 8 + lib/Unishox-1.0-shadinger/src/unishox.cpp | 601 ++++++++++++++++++ lib/Unishox-1.0-shadinger/src/unishox.h | 26 + tasmota/CHANGELOG.md | 1 + tasmota/my_user_config.h | 1 + tasmota/settings.h | 2 +- tasmota/settings.ino | 4 + tasmota/xdrv_10_rules.ino | 247 ++++++- 10 files changed, 1040 insertions(+), 15 deletions(-) create mode 100644 lib/Unishox-1.0-shadinger/generator/generator.c create mode 100644 lib/Unishox-1.0-shadinger/generator/remapping.xlsx create mode 100644 lib/Unishox-1.0-shadinger/library.properties.txt create mode 100644 lib/Unishox-1.0-shadinger/src/unishox.cpp create mode 100644 lib/Unishox-1.0-shadinger/src/unishox.h diff --git a/lib/Unishox-1.0-shadinger/generator/generator.c b/lib/Unishox-1.0-shadinger/generator/generator.c new file mode 100644 index 000000000..81c46649e --- /dev/null +++ b/lib/Unishox-1.0-shadinger/generator/generator.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2019 Siara Logics (cc) + * + * 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. + * + * @author Arundale R. + * + */ + +// Pre-compute c_95[] and l_95[] + +#include +#include +#include +#include +#include +#include + +typedef unsigned char byte; + +enum {SHX_SET1 = 0, SHX_SET1A, SHX_SET1B, SHX_SET2, SHX_SET3, SHX_SET4, SHX_SET4A}; +char us_vcodes[] = {0, 2, 3, 4, 10, 11, 12, 13, 14, 30, 31}; +char us_vcode_lens[] = {2, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5}; +char us_sets[][11] = + {{ 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'}, + {'.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'}, + {';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'}, + {'=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`'}}; + // {{ 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'}, + // {'.', ',', '-', '/', '=', '+', ' ', '(', ')', '$', '%'}, + // {'&', ';', ':', '<', '>', '*', '"', '{', '}', '[', ']'}, + // {'@', '?', '\'', '^', '#', '_', '!', '\\', '|', '~', '`'}}; + +unsigned int c_95[95] ; +unsigned char l_95[95] ; + + +void init_coder() { + for (int i = 0; i < 7; i++) { + for (int j = 0; j < 11; j++) { + char c = us_sets[i][j]; + if (c != 0 && c != 32) { + int ascii = c - 32; + //int prev_code = c_95[ascii]; + //int prev_code_len = l_95[ascii]; + switch (i) { + case SHX_SET1: // just us_vcode + c_95[ascii] = (us_vcodes[j] << (16 - us_vcode_lens[j])); + l_95[ascii] = us_vcode_lens[j]; + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + if (c >= 'a' && c <= 'z') { + ascii -= ('a' - 'A'); + //prev_code = c_95[ascii]; + //prev_code_len = l_95[ascii]; + c_95[ascii] = (2 << 12) + (us_vcodes[j] << (12 - us_vcode_lens[j])); + l_95[ascii] = 4 + us_vcode_lens[j]; + } + break; + case SHX_SET1A: // 000 + us_vcode + c_95[ascii] = 0 + (us_vcodes[j] << (13 - us_vcode_lens[j])); + l_95[ascii] = 3 + us_vcode_lens[j]; + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + if (c >= 'a' && c <= 'z') { + ascii -= ('a' - 'A'); + //prev_code = c_95[ascii]; + //prev_code_len = l_95[ascii]; + c_95[ascii] = (2 << 12) + 0 + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 4 + 3 + us_vcode_lens[j]; + } + break; + case SHX_SET1B: // 00110 + us_vcode + c_95[ascii] = (6 << 11) + (us_vcodes[j] << (11 - us_vcode_lens[j])); + l_95[ascii] = 5 + us_vcode_lens[j]; + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + if (c >= 'a' && c <= 'z') { + ascii -= ('a' - 'A'); + //prev_code = c_95[ascii]; + //prev_code_len = l_95[ascii]; + c_95[ascii] = (2 << 12) + (6 << 7) + (us_vcodes[j] << (7 - us_vcode_lens[j])); + l_95[ascii] = 4 + 5 + us_vcode_lens[j]; + } + break; + case SHX_SET2: // 0011100 + us_vcode + c_95[ascii] = (28 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + break; + case SHX_SET3: // 0011101 + us_vcode + c_95[ascii] = (29 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + break; + case SHX_SET4: // 0011110 + us_vcode + c_95[ascii] = (30 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + break; + case SHX_SET4A: // 0011111 + us_vcode + c_95[ascii] = (31 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + } + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + } + } + } + c_95[0] = 16384; + l_95[0] = 3; + +} + +int main(int argv, char *args[]) { + init_coder(); + + printf("uint16_t c_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("0x%04X", c_95[i]); + } + printf(" };\n"); + + printf("uint8_t l_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("%6d", l_95[i]); + } + printf(" };\n"); + + printf("\n\n"); + + printf("uint16_t c_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("%5d", c_95[i]); + } + printf(" };\n"); + + printf("uint8_t l_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("%5d", l_95[i]); + } + printf(" };\n"); + + + printf("uint16_t cl_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("0x%04X + %2d", c_95[i], l_95[i]); + } + printf(" };\n"); + +} \ No newline at end of file diff --git a/lib/Unishox-1.0-shadinger/generator/remapping.xlsx b/lib/Unishox-1.0-shadinger/generator/remapping.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..94a82ecded44048c4c5a025e887cee91c9aa3115 GIT binary patch literal 12338 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 ZCo(F^z`XV@0D$=V@p~P-GZ}yX`hU>t7()O6 literal 0 HcmV?d00001 diff --git a/lib/Unishox-1.0-shadinger/library.properties.txt b/lib/Unishox-1.0-shadinger/library.properties.txt new file mode 100644 index 000000000..138b2027c --- /dev/null +++ b/lib/Unishox-1.0-shadinger/library.properties.txt @@ -0,0 +1,8 @@ +name=Unishox Compressor Decompressor highly customized and optimized for ESP8266 and Tasmota +version=1.0 +author=Arundale Ramanathan, Stephan Hadinger +maintainer=Arun , Stephan +sentence=Unishox compression for Tasmota Rules +paragraph=It is based on Unishox hybrid encoding technique. This version has specific Unicode code removed for size. +url=https://github.com/siara-cc/Unishox +architectures=esp8266 diff --git a/lib/Unishox-1.0-shadinger/src/unishox.cpp b/lib/Unishox-1.0-shadinger/src/unishox.cpp new file mode 100644 index 000000000..ada408bff --- /dev/null +++ b/lib/Unishox-1.0-shadinger/src/unishox.cpp @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2019 Siara Logics (cc) + * + * 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. + * + * @author Arundale R. + * + */ + +/* + * + * 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. + * + * - moved to C++ (but still C-style) + * - c_95[] and l_95[] are pre-computed + * - all arrays in PROGMEM + * - removed all Unicode specific code to get code smaller, Unicode is rare in rules and encoded as pure binary + * - removed prev_lines management to reduce code size, we don't track previous encodings + * - using C++ const instead of #define + * - reusing the Unicode market to encode pure binary, which is 3 bits instead of 9 + * - reverse binary encoding to 255-byte, favoring short encoding for values above 127, typical of Unicode + * - remove 2 bits encoding for Counts, since it could lead to a series of more than 8 consecutive 0-bits and output NULL char. + * Minimum encoding is 5 bits, which means spending 3+1=4 more bits for values in the range 0..3 + * - removed CRLF encoding and reusing entry for RPT, saving 3 bits for repeats. Note: any CR will be binary encded + * - add safeguard to the output size (len_out), note that the compress buffer needs to be 4 bytes larger than actual compressed output. + * This is needed to avoid crash, since output can have ~30 bits + * - combined c_95[] and l_95[] to a single array to save space + * - Changed mapping of some characters in Set3, Set4 and Set4A, favoring frequent characters in rules and javascript + * - Added escape mechanism to ensure we never output NULL char. The marker is 0x2A which looked rare in preliminary tests + * + * @author Stephan Hadinger + * + */ + +#include +#include +#include +#include +#include +#include + +#include "unishox.h" + +typedef unsigned char byte; +// we squeeze both c_95[] and l_95[] in a sinle array. +// c_95[] uses only the 3 upper nibbles (or 12 most signifcant bits), while the last nibble encodes length (3..13) +uint16_t cl_95[95] PROGMEM = {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 }; +// Original version with c/l separate +// uint16_t c_95[95] PROGMEM = {0x4000, 0x3F80, 0x3D80, 0x3C80, 0x3BE0, 0x3E80, 0x3F40, 0x3EC0, 0x3BA0, 0x3BC0, 0x3D60, 0x3B60, 0x3A80, 0x3AC0, 0x3A00, 0x3B00, 0x38C0, 0x3900, 0x3940, 0x3960, 0x3980, 0x39A0, 0x39C0, 0x39E0, 0x39F0, 0x3880, 0x3CC0, 0x3C00, 0x3D00, 0x3E00, 0x3F00, 0x3B40, 0x3BF0, 0x2B00, 0x21C0, 0x20C0, 0x2100, 0x2600, 0x2300, 0x21E0, 0x2140, 0x2D00, 0x2358, 0x2340, 0x2080, 0x21A0, 0x2E00, 0x2C00, 0x2180, 0x2350, 0x2F80, 0x2F00, 0x2A00, 0x2160, 0x2330, 0x21F0, 0x2360, 0x2320, 0x2368, 0x3DE0, 0x3FA0, 0x3DF0, 0x3D40, 0x3F60, 0x3FF0, 0xB000, 0x1C00, 0x0C00, 0x1000, 0x6000, 0x3000, 0x1E00, 0x1400, 0xD000, 0x3580, 0x3400, 0x0800, 0x1A00, 0xE000, 0xC000, 0x1800, 0x3500, 0xF800, 0xF000, 0xA000, 0x1600, 0x3300, 0x1F00, 0x3600, 0x3200, 0x3680, 0x3DA0, 0x3FC0, 0x3DC0, 0x3FE0 }; +// uint8_t l_95[95] PROGMEM = { 3, 11, 11, 10, 12, 10, 11, 10, 11, 11, 11, 11, 10, 10, 9, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 10, 10, 9, 10, 9, 10, 11, 12, 8, 11, 10, 10, 7, 11, 12, 11, 8, 13, 12, 10, 11, 8, 8, 11, 13, 9, 9, 8, 11, 12, 12, 13, 12, 13, 12, 11, 12, 11, 11, 12, 4, 7, 6, 6, 3, 7, 8, 7, 4, 9, 8, 6, 7, 4, 4, 7, 9, 5, 5, 4, 7, 8, 8, 9, 8, 9, 11, 11, 11, 12 }; + +enum {SHX_STATE_1 = 1, SHX_STATE_2}; // removed Unicode state + +enum {SHX_SET1 = 0, SHX_SET1A, SHX_SET1B, SHX_SET2, SHX_SET3, SHX_SET4, SHX_SET4A}; +// changed mapping in Set3, Set4, Set4A to accomodate frequencies in Rules and Javascript +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'}, + {'.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'}, + {';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'}, + {'=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`'}}; + // {{ 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'}, + // {'.', ',', '-', '/', '=', '+', ' ', '(', ')', '$', '%'}, + // {'&', ';', ':', '<', '>', '*', '"', '{', '}', '[', ']'}, + // {'@', '?', '\'', '^', '#', '_', '!', '\\', '|', '~', '`'}}; + +// Decoder is designed for using less memory, not speed +// Decode lookup table for code index and length +// First 2 bits 00, Next 3 bits indicate index of code from 0, +// last 3 bits indicate code length in bits +// 0, 1, 2, 3, 4, +char us_vcode[32] PROGMEM = + {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, +char us_hcode[32] PROGMEM = + {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)}; + +const char ESCAPE_MARKER = 0x2A; // Escape any null char + +const uint16_t TERM_CODE = 0x37C0; // 0b0011011111000000 +const uint16_t TERM_CODE_LEN = 10; +const uint16_t DICT_CODE = 0x0000; +const uint16_t DICT_CODE_LEN = 5; +const uint16_t DICT_OTHER_CODE = 0x0000; // not used +const uint16_t DICT_OTHER_CODE_LEN = 6; +// const uint16_t RPT_CODE = 0x2370; +// const uint16_t RPT_CODE_LEN = 13; +const uint16_t RPT_CODE_TASMOTA = 0x3780; +const uint16_t RPT_CODE_TASMOTA_LEN = 10; +const uint16_t BACK2_STATE1_CODE = 0x2000; // 0010 = back to lower case +const uint16_t BACK2_STATE1_CODE_LEN = 4; +const uint16_t BACK_FROM_UNI_CODE = 0xFE00; +const uint16_t BACK_FROM_UNI_CODE_LEN = 8; +// const uint16_t CRLF_CODE = 0x3780; +// const uint16_t CRLF_CODE_LEN = 10; +const uint16_t LF_CODE = 0x3700; +const uint16_t LF_CODE_LEN = 9; +const uint16_t TAB_CODE = 0x2400; +const uint16_t TAB_CODE_LEN = 7; +// const uint16_t UNI_CODE = 0x8000; // Unicode disabled +// const uint16_t UNI_CODE_LEN = 3; +// const uint16_t UNI_STATE_SPL_CODE = 0xF800; +// const uint16_t UNI_STATE_SPL_CODE_LEN = 5; +// const uint16_t UNI_STATE_DICT_CODE = 0xFC00; +// const uint16_t UNI_STATE_DICT_CODE_LEN = 7; +// const uint16_t CONT_UNI_CODE = 0x2800; +// const uint16_t CONT_UNI_CODE_LEN = 7; +const uint16_t ALL_UPPER_CODE = 0x2200; +const uint16_t ALL_UPPER_CODE_LEN = 8; +const uint16_t SW2_STATE2_CODE = 0x3800; +const uint16_t SW2_STATE2_CODE_LEN = 7; +const uint16_t ST2_SPC_CODE = 0x3B80; +const uint16_t ST2_SPC_CODE_LEN = 11; +const uint16_t BIN_CODE_TASMOTA = 0x8000; +const uint16_t BIN_CODE_TASMOTA_LEN = 3; +// const uint16_t BIN_CODE = 0x2000; +// const uint16_t BIN_CODE_LEN = 9; + +#define NICE_LEN 5 + +// 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; + } + //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; +} + +// First five bits are code and Last three bits of codes represent length +// removing last 2 bytes, unused, we will never have values above 600 bytes +// const byte codes[7] = {0x01, 0x82, 0xC3, 0xE5, 0xED, 0xF5, 0xFD}; +// const byte bit_len[7] = {2, 5, 7, 9, 12, 16, 17}; +// const uint16_t adder[7] = {0, 4, 36, 164, 676, 4772, 0}; +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) { + int till = 0; + int base = 0; + for (int 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); + // 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; + } + base = till; + } + return ol; +} + +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; + 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; + if (match_len > longest_len) { + longest_len = match_len; + longest_dist = match_dist; + } + } + } + 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); + } + *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; + } + return -l; +} + +// Compress a buffer. +// Inputs: +// - in: non-null pointer to a buffer of bytes to be compressed. Progmem is not valid. Null bytes are valid. +// - len: size of the input buffer. 0 is valid for empty buffer +// - out: pointer to output buffer. out is nullptr, the compressor does a dry-run and reports the compressed size without writing bytes +// - len_out: length in bytes of the output buffer. +// 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) { + + char *ptr; + byte bits; + byte state; + + int l, ll, ol; + char c_in, c_next; + byte is_upper, is_all_upper; + + ol = 0; + state = SHX_STATE_1; + is_all_upper = 0; + for (l=0; l 0) { + continue; + } + l = -l; + } + if (state == SHX_STATE_2) { // if Set2 + if ((c_in >= ' ' && c_in <= '@') || + (c_in >= '[' && c_in <= '`') || + (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); + } + } + + is_upper = 0; + if (c_in >= 'A' && c_in <= '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 = in[l+1]; + + if (c_in >= 32 && c_in <= 126) { + if (is_upper && !is_all_upper) { + for (ll=l+5; ll>=l && ll 'Z') + break; + } + 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 && c_in >= '0' && c_in <= '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 && 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 + 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); + } + } 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 { + 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); + } + + // check that we have some headroom in the output buffer + if (ol / 8 >= len_out - 4) { + return -1; // we risk overflow and crash + } + } + + 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/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--; + } + return (c_in & (0x80 >> (bit_no % 8)) ? 1 << count : 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; + do { + // detect marker + if (ESCAPE_MARKER == in[*bit_no_p >> 3]) { + *bit_no_p += 8; // skip marker + } + if (*bit_no_p >= len) + return -1; // invalid state + code += getBitVal(in, *bit_no_p, count); + (*bit_no_p)++; + 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 +} + +int getNumFromBits(const char *in, int bit_no, int count) { + int ret = 0; + while (count--) { + if (ESCAPE_MARKER == in[bit_no >> 3]) { + bit_no += 8; // skip marker + } + ret += getBitVal(in, bit_no++, count); + } + return ret; +} + +// const byte bit_len[7] = {5, 2, 7, 9, 12, 16, 17}; +// const uint16_t adder[7] = {4, 0, 36, 164, 676, 4772, 0}; + +// byte bit_len[7] PROGMEM = { 5, 7, 9, 12, 16 }; +// byte bit_len_read[7] PROGMEM = {5, 2, 7, 9, 12, 16 }; +// uint16_t adder_read[7] PROGMEM = {4, 0, 36, 164, 676, 4772, 0}; +// uint16_t adder_read[] PROGMEM = {0, 0, 32, 160, 672, 4768 }; + +// byte bit_len[7] PROGMEM = { 5, 7, 9, 12, 16 }; +// 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); + 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 + + int base; + int till = 0; + byte bit_len_idx; // bit_len[0] + for (uint32_t i = 0; i <= idx; i++) { + base = till; + 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; + + (*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; + 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) { + + int dstate; + int bit_no; + byte is_all_upper; + + int 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) { + int 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 + 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 + 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 = is_all_upper = 0; + continue; + } + v = getCodeIdx(us_vcode, in, len, &bit_no); // read again vCode + if (v < 0) break; // end of stream + if (v == 0) { + h = getCodeIdx(us_hcode, in, 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; + } + } else + if (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 = getCodeIdx(us_vcode, in, len, &bit_no); // 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 + } else { + ol = decodeRepeat(in, len, out, ol, &bit_no); // dist + } + continue; + } + + if (h == SHX_SET1 && v == 3) { + // was Unicode, will do Binary instead + out[ol++] = 255 - readCount(in, &bit_no, len); // binary + continue; + } + if (h < 7 && v < 11) // TODO: are these the actual limits? Not 11x7 ? + c = pgm_read_byte(&sets[h][v]); + if (c >= 'a' && c <= 'z') { + if (is_upper) + c -= 32; // go to UpperCase for letters + } else { // handle all other cases + if (is_upper && dstate == SHX_SET1 && v == 1) + 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; + if (ol + count >= len_out) { + return -1; // overflow + } + char rpt_c = out[ol - 1]; + while (count--) + out[ol++] = rpt_c; + continue; + } + if (10 == v) { + break; // TERM, stop decoding + } + } + } + out[ol++] = c; + + if (ol >= len_out) { + return -1; // overflow + } + } + + return ol; + +} diff --git a/lib/Unishox-1.0-shadinger/src/unishox.h b/lib/Unishox-1.0-shadinger/src/unishox.h new file mode 100644 index 000000000..4d6b81641 --- /dev/null +++ b/lib/Unishox-1.0-shadinger/src/unishox.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Siara Logics (cc) + * + * 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. + * + * @author Arundale R. + * + */ +#ifndef unishox +#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); + +#endif + diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 5a35b9b6d..eeae3def4 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -11,6 +11,7 @@ - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz - Change PWM updated to the latest version of Arduino PR #7231 +- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules ### 8.2.0.5 20200425 diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 72a763b3c..ce93a6eed 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -397,6 +397,7 @@ // -- Rules or Script ---------------------------- // Select none or only one of the below defines #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_SCRIPT // Add support for script (+17k code) //#define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support diff --git a/tasmota/settings.h b/tasmota/settings.h index 3925aa30d..edd3de523 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -112,7 +112,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t only_json_message : 1; // bit 8 (v8.2.0.3) - SetOption90 - Disable non-json MQTT response uint32_t fade_at_startup : 1; // bit 9 (v8.2.0.3) - SetOption91 - Enable light fading at start/power on uint32_t pwm_ct_mode : 1; // bit 10 (v8.2.0.4) - SetOption92 - Set PWM Mode from regular PWM to ColorTemp control (Xiaomi Philips ...) - uint32_t spare11 : 1; + uint32_t compress_rules_cpu : 1; // bit 11 (v8.2.0.6) - SetOption93 - Keep uncompressed rules in memory to avoid CPU load of uncompressing at each tick uint32_t spare12 : 1; uint32_t spare13 : 1; uint32_t spare14 : 1; diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 83527ea6f..de890ee41 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -1404,6 +1404,10 @@ void SettingsDelta(void) Settings.module = WEMOS; ModuleDefault(WEMOS); #endif // ESP32 + // make sure the empty rules have two consecutive NULLs, to be compatible with compressed rules + if (Settings.rules[0][0] == 0) { Settings.rules[0][1] = 0; } + if (Settings.rules[1][0] == 0) { Settings.rules[1][1] = 0; } + if (Settings.rules[2][0] == 0) { Settings.rules[2][1] = 0; } } Settings.version = VERSION; diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index fa5841564..c88806ced 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -66,6 +66,8 @@ #define XDRV_10 10 +#include + #define D_CMND_RULE "Rule" #define D_CMND_RULETIMER "RuleTimer" #define D_CMND_EVENT "Event" @@ -178,6 +180,222 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; #error MAX_RULE_MEMS is bigger than 16 #endif + +/*******************************************************************************************/ +/* + * Add Unishox compression to Rules + * + * New compression for Rules, depends on SetOption93 + * + * To avoid memory corruption when downgrading, the format is as follows: + * - If `SetOption93 0` + * Rule[x][] = 511 char max NULL terminated string (512 with trailing NULL) + * Rule[x][0] = 0 if the Rule is empty + * New: in case the string is empty we also enforce: + * Rule[x][1] = 0 (i.e. we have two conseutive NULLs) + * + * - If `SetOption93 1` + * If the rule is smaller than 511, it is stored uncompressed. Rule[x][0] is not null. + * If the rule is empty, Rule[x][0] = 0 and Rule[x][1] = 0; + * If the rule is bigger than 511, it is stored compressed + * The first byte of each Rule is always NULL. + * Rule[x][0] = 0, if firmware is downgraded, the rule will be considered as empty + * + * The second byte contains the size of uncompressed rule in 8-bytes blocks (i.e. (len+7)/8 ) + * Maximum rule size si 2KB (2048 bytes per rule), although there is little chances compression ratio will go down to 75% + * Rule[x][1] = size uncompressed in dwords. If zero, the rule is empty. + * + * The remaining bytes contain the compressed rule, NULL terminated + */ +/*******************************************************************************************/ + +#ifdef USE_RULES_COMPRESSION +// Statically allocate one String per rule +String k_rules[MAX_RULE_SETS] = { String(), String(), String() }; // Strings are created empty +#endif // USE_RULES_COMPRESSION + +// Returns whether the rule is uncompressed, which means the first byte is not NULL +inline bool IsRuleUncompressed(uint32_t idx) { +#ifdef USE_RULES_COMPRESSION + return Settings.rules[idx][0] ? true : false; // first byte not NULL, the rule is not empty and not compressed +#else + return true; +#endif +} + +// Returns whether the rule is empty, which requires two consecutive NULL +inline bool IsRuleEmpty(uint32_t idx) { +#ifdef USE_RULES_COMPRESSION + return (Settings.rules[idx][0] == 0) && (Settings.rules[idx][1] == 0) ? true : false; +#else + return (Settings.rules[idx][0] == 0) ? true : false; +#endif +} + +// Returns the approximate (+3-0) length of the rule, not counting the trailing NULL +size_t GetRuleLen(uint32_t idx) { + // no need to use #ifdef USE_RULES_COMPRESSION, the compiler will optimize since first test is always true + if (IsRuleUncompressed(idx)) { + return strlen(Settings.rules[idx]); + } else { // either empty or compressed + return Settings.rules[idx][1] * 8; // cheap calculation, but not byte accurate (may overshoot by 7) + } +} + +// Returns the actual Flash storage for the Rule, including trailing NULL +size_t GetRuleLenStorage(uint32_t idx) { + // no need to use #ifdef USE_RULES_COMPRESSION, the compiler will optimize since first test is always true + if (IsRuleUncompressed(idx)) { + return 1 + strlen(Settings.rules[idx]); + } else { + return 2 + strlen(&Settings.rules[idx][2]); // skip first byte and get len of the compressed rule + } +} + +// 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 + rule_head++; // advance to the actual compressed buffer + + // We use a nasty trick here. To avoid allocating twice the buffer, + // we first extend the buffer of the String object to the target size (maybe overshooting by 7 bytes) + // then we decompress in this buffer, + // and finally assign the raw string to the String, which happens to work: String uses memmove(), so overlapping works + rule.reserve(buf_len); + char* buf = rule.begin(); + + int32_t len_decompressed = 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) +} + +// +// Read rule in memory, uncompress if needed +// +// Returns: String() object containing a copy of the rule (rule processing is destructive and will change the String) +String GetRule(uint32_t idx) { + if (IsRuleUncompressed(idx)) { + return String(Settings.rules[idx]); + } else { +#ifdef USE_RULES_COMPRESSION // we still do #ifdef to make sure we don't link unnecessary code + + String rule(""); + if (Settings.rules[idx][2] == 0) { return rule; } // the rule is empty + + // If the cache is empty, we need to decompress from Settings + if (0 == k_rules[idx].length() ) { + GetRule_decompress(rule, &Settings.rules[idx][1]); + if (!Settings.flag4.compress_rules_cpu) { + k_rules[idx] = rule; // keep a copy for next time + } + } else { + // we have a valid copy + rule = k_rules[idx]; + } + return rule; +#endif + } +} + +#ifdef USE_RULES_COMPRESSION +// internal function, comrpess rule and store a cached version uncompressed (except if SetOption94 1) +// 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); + + 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 + k_rules[idx] = (const char*) nullptr; // Assign the String to nullptr, clears previous string and disallocate internal buffers of String object + if ((!Settings.flag4.compress_rules_cpu) && out) { // if out == nullptr, don't store cache + // keep copy in cache + k_rules[idx] = in; + } + } + return len_compressed; +} +#endif // USE_RULES_COMPRESSION + +// Returns: +// >= 0 : the actual stored size +// <0 : not enough space +int32_t SetRule(uint32_t idx, const char *content, bool append = false) { + if (nullptr == content) { content = ""; } // if nullptr, use empty string + size_t len_in = strlen(content); + bool needsCompress = false; + size_t offset = 0; + + if (len_in >= MAX_RULE_SIZE) { // if input is more than 512, it will not fit uncompressed + needsCompress = true; + } + if (append) { + if (IsRuleUncompressed(idx) || IsRuleEmpty(idx)) { // if already uncompressed (so below 512) and append mode, check if it still fits uncompressed + offset = strlen(Settings.rules[idx]); + if (len_in + offset >= MAX_RULE_SIZE) { + needsCompress = true; + } + } else { + needsCompress = true; // we append to a non-empty compressed rule, so it won't fit uncompressed + } + } + + if (!needsCompress) { // the rule fits uncompressed, so just copy it + strlcpy(Settings.rules[idx] + offset, content, sizeof(Settings.rules[idx])); + +#ifdef USE_RULES_COMPRESSION + // do a dry-run compression to display how much it would be compressed + 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); + 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)); + +#endif // USE_RULES_COMPRESSION + + return len_in + offset; + } else { +#ifdef USE_RULES_COMPRESSION + int32_t len_compressed; + // allocate temp buffer so we don't nuke the rule if it's too big to fit + char *buf_out = (char*) malloc(MAX_RULE_SIZE + 8); // take some margin + if (!buf_out) { return -1; } // fail if couldn't allocate + + // compress + if (append) { + String content_append = GetRule(idx); // get original Rule and decompress it if needed + content_append += content; // concat new content + len_in = content_append.length(); // adjust length + len_compressed = SetRule_compress(idx, content_append.c_str(), len_in, buf_out, MAX_RULE_SIZE + 8); + } else { + len_compressed = SetRule_compress(idx, content, len_in, buf_out, MAX_RULE_SIZE + 8); + } + + if ((len_compressed >= 0) && (len_compressed < MAX_RULE_SIZE - 2)) { + // size is ok, copy to Settings + Settings.rules[idx][0] = 0; // clear first byte to mark as compressed + Settings.rules[idx][1] = (len_in + 7) / 8; // store original length in first bytes (4 bytes chuks) + memcpy(&Settings.rules[idx][2], buf_out, len_compressed); + Settings.rules[idx][len_compressed + 2] = 0; // add NULL termination + AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Compressed from %d to %d (-%d%%)"), len_in, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_in, 0, 100)); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: First bytes: %02X%02X%02X%02X"), Settings.rules[idx][0], Settings.rules[idx][1], Settings.rules[idx][2], Settings.rules[idx][3]); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: GetRuleLenStorage = %d"), GetRuleLenStorage(idx)); + } else { + len_compressed = -1; // failed + // clear rule cache, so it will be reloaded from Settings + k_rules[idx] = (const char *) nullptr; + } + free(buf_out); + return len_compressed; + +#else // USE_RULES_COMPRESSION + return -1; // the rule does not fit and we can't compress +#endif // USE_RULES_COMPRESSION + } + +} + /*******************************************************************************************/ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) @@ -419,7 +637,7 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved) //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event = %s, Rule = %s"), event_saved.c_str(), Settings.rules[rule_set]); - String rules = Settings.rules[rule_set]; + String rules = GetRule(rule_set); Rules.trigger_count[rule_set] = 0; int plen = 0; @@ -531,7 +749,7 @@ bool RulesProcessEvent(char *json_event) //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event %s"), event_saved.c_str()); for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { - if (strlen(Settings.rules[i]) && bitRead(Settings.rule_enabled, i)) { + if (GetRuleLen(i) && bitRead(Settings.rule_enabled, i)) { if (RuleSetProcess(i, event_saved)) { serviced = true; } } } @@ -547,7 +765,7 @@ void RulesInit(void) { rules_flag.data = 0; for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { - if (Settings.rules[i][0] == '\0') { + if (0 == GetRuleLen(i)) { bitWrite(Settings.rule_enabled, i, 0); bitWrite(Settings.rule_once, i, 0); } @@ -1727,7 +1945,8 @@ void CmndRule(void) { uint8_t index = XdrvMailbox.index; if ((index > 0) && (index <= MAX_RULE_SETS)) { - if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules[index -1]))) { + // if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules[index -1]))) { // TODO postpone size calculation + if (XdrvMailbox.data_len > 0) { // TODO postpone size calculation if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { switch (XdrvMailbox.payload) { case 0: // Off @@ -1753,24 +1972,24 @@ void CmndRule(void) break; } } else { - int offset = 0; + bool append = false; if ('+' == XdrvMailbox.data[0]) { - offset = strlen(Settings.rules[index -1]); - if (XdrvMailbox.data_len < (sizeof(Settings.rules[index -1]) - offset -1)) { // Check free space - XdrvMailbox.data[0] = ' '; // Remove + and make sure at least one space is inserted - } else { - offset = -1; // Not enough space so skip it - } + XdrvMailbox.data[0] = ' '; // Remove + and make sure at least one space is inserted + append = true; } - if (offset != -1) { - strlcpy(Settings.rules[index -1] + offset, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.rules[index -1])); + int32_t res = SetRule(index - 1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, append); + if (res < 0) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("RUL: not enough space")); } } Rules.triggers[index -1] = 0; // Reset once flag } + // snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"), + // XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), + // GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]); snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"), XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), - GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]); + GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[0]) - GetRuleLenStorage(index - 1), GetRule(index - 1).c_str()); } } From d9c9eeca78ea74e25a3101c32976c715b689825b Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Fri, 8 May 2020 19:45:39 +0200 Subject: [PATCH 07/30] Update Italian language --- tasmota/language/it_IT.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 5c5c8a537..c9710ef01 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -674,7 +674,7 @@ #define D_SENSOR_HRXL_RX "HRXL - RX" #define D_SENSOR_ELECTRIQ_MOODL "MOODL - TX" #define D_SENSOR_AS3935 "AS3935" -#define D_SENSOR_WINDMETER_SPEED "WindMeter Spd" +#define D_SENSOR_WINDMETER_SPEED "Velocità vento" #define D_GPIO_WEBCAM_PWDN "CAM_PWDN" #define D_GPIO_WEBCAM_RESET "CAM_RESET" #define D_GPIO_WEBCAM_XCLK "CAM_XCLK" @@ -800,7 +800,7 @@ #define D_AS3935_CAL_OK "calibrazione impostata a:" //xsns_68_opentherm.ino -#define D_SENSOR_BOILER_OT_RX "OpenTherm RX" -#define D_SENSOR_BOILER_OT_TX "OpenTherm TX" +#define D_SENSOR_BOILER_OT_RX "OpenTherm - RX" +#define D_SENSOR_BOILER_OT_TX "OpenTherm - TX" #endif // _LANGUAGE_IT_IT_H_ From ed33b9c76ba2a8c195abe84ce385033dcd8622a0 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 19:56:27 +0200 Subject: [PATCH 08/30] Quick fix for ESP32 compilation --- lib/Unishox-1.0-shadinger/src/unishox.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Unishox-1.0-shadinger/src/unishox.cpp b/lib/Unishox-1.0-shadinger/src/unishox.cpp index ada408bff..050d9338c 100644 --- a/lib/Unishox-1.0-shadinger/src/unishox.cpp +++ b/lib/Unishox-1.0-shadinger/src/unishox.cpp @@ -53,6 +53,10 @@ #include "unishox.h" +#ifndef PROGMEM // quick fix for ESP32 compilation +#define PROGMEM +#endif + typedef unsigned char byte; // we squeeze both c_95[] and l_95[] in a sinle array. // c_95[] uses only the 3 upper nibbles (or 12 most signifcant bits), while the last nibble encodes length (3..13) From bf829765e4479955abda391265d7ff2371b0b99b Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 21:05:37 +0200 Subject: [PATCH 09/30] Fix compilation for ESP32 --- lib/Unishox-1.0-shadinger/src/unishox.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/Unishox-1.0-shadinger/src/unishox.cpp b/lib/Unishox-1.0-shadinger/src/unishox.cpp index 050d9338c..72c9d3915 100644 --- a/lib/Unishox-1.0-shadinger/src/unishox.cpp +++ b/lib/Unishox-1.0-shadinger/src/unishox.cpp @@ -51,12 +51,9 @@ #include #include +#include #include "unishox.h" -#ifndef PROGMEM // quick fix for ESP32 compilation -#define PROGMEM -#endif - typedef unsigned char byte; // we squeeze both c_95[] and l_95[] in a sinle array. // c_95[] uses only the 3 upper nibbles (or 12 most signifcant bits), while the last nibble encodes length (3..13) From 860db1b1d394e8833212e55d145aa42c34c9aecb Mon Sep 17 00:00:00 2001 From: Michael Ingraham <34340210+meingraham@users.noreply.github.com> Date: Sat, 9 May 2020 10:41:56 -0400 Subject: [PATCH 10/30] FIRMWARE_BASIC remnant --- tasmota/tasmota_configurations.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index 3fc3cc0e0..a3494a6c7 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -505,7 +505,7 @@ #undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef DEBUG_THEO // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code -#endif // FIRMWARE_BASIC +#endif // FIRMWARE_LITE /*********************************************************************************************\ * [tasmota-minimal.bin] From 64c71970cbcbfa6d8ffba49145bf5da2d46ab3c2 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sat, 9 May 2020 18:05:13 +0200 Subject: [PATCH 11/30] Fix clearing rules --- tasmota/xdrv_10_rules.ino | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index c88806ced..ba74c08f8 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -244,12 +244,15 @@ size_t GetRuleLen(uint32_t idx) { // Returns the actual Flash storage for the Rule, including trailing NULL size_t GetRuleLenStorage(uint32_t idx) { - // no need to use #ifdef USE_RULES_COMPRESSION, the compiler will optimize since first test is always true - if (IsRuleUncompressed(idx)) { - return 1 + strlen(Settings.rules[idx]); +#ifdef USE_RULES_COMPRESSION + if (Settings.rules[idx][0] || !Settings.rules[idx][1]) { // if first byte is non-NULL it is uncompressed, if second byte is NULL, then it's either uncompressed or empty + return 1 + strlen(Settings.rules[idx]); // uncompressed or empty } else { - return 2 + strlen(&Settings.rules[idx][2]); // skip first byte and get len of the compressed rule + return 2 + strlen(&Settings.rules[idx][1]); // skip first byte and get len of the compressed rule } +#else + return 1 + strlen(Settings.rules[idx]); +#endif } // internal function, do the actual decompression @@ -282,7 +285,7 @@ String GetRule(uint32_t idx) { #ifdef USE_RULES_COMPRESSION // we still do #ifdef to make sure we don't link unnecessary code String rule(""); - if (Settings.rules[idx][2] == 0) { return rule; } // the rule is empty + if (Settings.rules[idx][1] == 0) { return rule; } // the rule is empty // If the cache is empty, we need to decompress from Settings if (0 == k_rules[idx].length() ) { @@ -343,14 +346,19 @@ int32_t SetRule(uint32_t idx, const char *content, bool append = false) { if (!needsCompress) { // the rule fits uncompressed, so just copy it strlcpy(Settings.rules[idx] + offset, content, sizeof(Settings.rules[idx])); + if (0 == Settings.rules[idx][0]) { + Settings.rules[idx][1] = 0; + } #ifdef USE_RULES_COMPRESSION - // do a dry-run compression to display how much it would be compressed - int32_t len_compressed, len_uncompressed; + if (0 != len_in + offset) { + // do a dry-run compression to display how much it would be compressed + 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); - 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)); + len_uncompressed = strlen(Settings.rules[idx]); + len_compressed = 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)); + } #endif // USE_RULES_COMPRESSION From 92c05faa8b29b87220b4a4a0593ccd0d618eb2fc Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 9 May 2020 19:22:12 +0200 Subject: [PATCH 12/30] Add root level triggers as discussed on Discord - Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command --- RELEASENOTES.md | 2 ++ tasmota/CHANGELOG.md | 3 ++- tasmota/xdrv_10_rules.ino | 23 ++++++++++++++++------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e86665a5d..00e1c4cf3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -103,3 +103,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt +- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules +- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index eeae3def4..5abf5c781 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -6,12 +6,13 @@ - Add support for analog anemometer by Matteo Albinola (#8283) - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) +- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules +- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz - Change PWM updated to the latest version of Arduino PR #7231 -- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules ### 8.2.0.5 20200425 diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index ba74c08f8..f83b23bb6 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -47,6 +47,7 @@ * on button1#state do publish cmnd/ring2/power %value% endon on button2#state do publish cmnd/strip1/power %value% endon * on switch1#state do power2 %value% endon * on analog#a0div10 do publish cmnd/ring2/dimmer %value% endon + * on root#loadavg<50 do power 2 endon * * Notes: * Spaces after , around and before are mandatory @@ -186,25 +187,25 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; * Add Unishox compression to Rules * * New compression for Rules, depends on SetOption93 - * + * * To avoid memory corruption when downgrading, the format is as follows: * - If `SetOption93 0` * Rule[x][] = 511 char max NULL terminated string (512 with trailing NULL) * Rule[x][0] = 0 if the Rule is empty * New: in case the string is empty we also enforce: * Rule[x][1] = 0 (i.e. we have two conseutive NULLs) - * + * * - If `SetOption93 1` * If the rule is smaller than 511, it is stored uncompressed. Rule[x][0] is not null. * If the rule is empty, Rule[x][0] = 0 and Rule[x][1] = 0; * If the rule is bigger than 511, it is stored compressed * The first byte of each Rule is always NULL. * Rule[x][0] = 0, if firmware is downgraded, the rule will be considered as empty - * + * * The second byte contains the size of uncompressed rule in 8-bytes blocks (i.e. (len+7)/8 ) * Maximum rule size si 2KB (2048 bytes per rule), although there is little chances compression ratio will go down to 75% * Rule[x][1] = size uncompressed in dwords. If zero, the rule is empty. - * + * * The remaining bytes contain the compressed rule, NULL terminated */ /*******************************************************************************************/ @@ -490,13 +491,21 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); + StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); if (!root.success()) { return false; } // No valid JSON data - if (!root[rule_task].success()) { return false; } // No rule_task in JSON data - JsonObject &obj1 = root[rule_task]; - JsonObject *obj = &obj1; + JsonObject *obj; + if (rule_task.startsWith("ROOT")) { // Support root level + obj = &root; + } else { + if (!root[rule_task].success()) { return false; } // No rule_task in JSON data + JsonObject &obj1 = root[rule_task]; + obj = &obj1; + } + String subtype; uint32_t i = 0; while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT" From bf215b1b9df24e1fe34745576f2c9d5a58995dc2 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 9 May 2020 19:33:19 +0200 Subject: [PATCH 13/30] Update root level triggers Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command --- RELEASENOTES.md | 2 +- tasmota/CHANGELOG.md | 2 +- tasmota/xdrv_10_rules.ino | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 00e1c4cf3..aba4ced89 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -104,4 +104,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 5abf5c781..f02a4c320 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -7,7 +7,7 @@ - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index f83b23bb6..476a1d392 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -491,14 +491,14 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } -//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); +AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); if (!root.success()) { return false; } // No valid JSON data JsonObject *obj; - if (rule_task.startsWith("ROOT")) { // Support root level + if ((rule_task.length() == 0) || rule_task.startsWith("ROOT")) { // Support root level obj = &root; } else { if (!root[rule_task].success()) { return false; } // No rule_task in JSON data From cc2f7cbf5a15c5a9bca4d4a1197bcb20300d0374 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 9 May 2020 19:35:16 +0200 Subject: [PATCH 14/30] Oops remove debug message --- tasmota/xdrv_10_rules.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index 476a1d392..f790768b9 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -491,7 +491,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } -AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); From de40260f3eaa3bf1d7ce690af76c963ac8649d67 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 10 May 2020 10:18:23 +0200 Subject: [PATCH 15/30] Fix BatteryPercentage calculation --- tasmota/xdrv_23_zigbee_5_converters.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index a0c59f20b..07b5f5ef1 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -703,7 +703,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { Zuint16, Cx0001, 0x0000, Z(MainsVoltage), &Z_Copy }, { Zuint8, Cx0001, 0x0001, Z(MainsFrequency), &Z_Copy }, { Zuint8, Cx0001, 0x0020, Z(BatteryVoltage), &Z_FloatDiv10 }, - { Zuint8, Cx0001, 0x0021, Z(BatteryPercentage), &Z_Copy }, + { Zuint8, Cx0001, 0x0021, Z(BatteryPercentage), &Z_FloatDiv2 }, // Device Temperature Configuration cluster { Zint16, Cx0002, 0x0000, Z(CurrentTemperature), &Z_Copy }, From 89b130b45a1b1ed6e3f716a10bed64681a83e98b Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 11:09:34 +0200 Subject: [PATCH 16/30] Change root rule trigger Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command --- RELEASENOTES.md | 2 +- tasmota/CHANGELOG.md | 2 +- tasmota/xdrv_10_rules.ino | 39 +++++++++++++++------------------------ 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index aba4ced89..24e65e4b0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -104,4 +104,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index f02a4c320..a54809a68 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -7,7 +7,7 @@ - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index f790768b9..f2f01a8db 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -417,19 +417,20 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) char stemp[10]; // Step1: Analyse rule - int pos = rule.indexOf('#'); - if (pos == -1) { return false; } // No # sign in rule - - String rule_task = rule.substring(0, pos); // "INA219" or "SYSTEM" + String rule_expr = rule; // "TELE-INA219#CURRENT>0.100" if (Rules.teleperiod) { - int ppos = rule_task.indexOf("TELE-"); // "TELE-INA219" or "INA219" + int ppos = rule_expr.indexOf("TELE-"); // "TELE-INA219#CURRENT>0.100" or "INA219#CURRENT>0.100" if (ppos == -1) { return false; } // No pre-amble in rule - rule_task = rule.substring(5, pos); // "INA219" or "SYSTEM" + rule_expr = rule.substring(5); // "INA219#CURRENT>0.100" or "SYSTEM#BOOT" } - String rule_expr = rule.substring(pos +1); // "CURRENT>0.100" or "BOOT" or "%var1%" or "MINUTE|5" String rule_name, rule_param; - int8_t compareOperator = parseCompareExpression(rule_expr, rule_name, rule_param); //Parse the compare expression.Return operator and the left, right part of expression + int8_t compareOperator = parseCompareExpression(rule_expr, rule_name, rule_param); // Parse the compare expression.Return operator and the left, right part of expression + + // rule_name = "INA219#CURRENT" + // rule_param = "0.100" or "%VAR1%" + +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: expr %s, name %s, param %s"), rule_expr.c_str(), rule_name.c_str(), rule_param.c_str()); char rule_svalue[80] = { 0 }; float rule_value = 0; @@ -477,11 +478,12 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) if (temp_value > -1) { rule_value = temp_value; } else { - rule_value = CharToFloat((char*)rule_svalue); // 0.1 - This saves 9k code over toFLoat()! + rule_value = CharToFloat((char*)rule_svalue); // 0.1 - This saves 9k code over toFLoat()! } } - // Step2: Search rule_task and rule_name + // Step2: Search rule_name + int pos; int rule_name_idx = 0; if ((pos = rule_name.indexOf("[")) > 0) { // "SUBTYPE1#CURRENT[1]" rule_name_idx = rule_name.substring(pos +1).toInt(); @@ -491,21 +493,10 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } -//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); - StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); if (!root.success()) { return false; } // No valid JSON data - - JsonObject *obj; - if ((rule_task.length() == 0) || rule_task.startsWith("ROOT")) { // Support root level - obj = &root; - } else { - if (!root[rule_task].success()) { return false; } // No rule_task in JSON data - JsonObject &obj1 = root[rule_task]; - obj = &obj1; - } - + JsonObject *obj = &root; String subtype; uint32_t i = 0; while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT" @@ -524,8 +515,8 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) str_value = (*obj)[rule_name]; // "CURRENT" } -//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"), -// rule_task.c_str(), rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none"); +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"), +// rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none"); Rules.event_value = str_value; // Prepare %value% From 02f61a5259d7418dedc5c6bd0fdb35914d084392 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 11:10:55 +0200 Subject: [PATCH 17/30] Allow rule processing on single Status command Allow rule processing on single Status command only --- tasmota/support_command.ino | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index d4b4337c9..1bd243754 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -561,6 +561,11 @@ void CmndStatus(void) #ifdef USE_SCRIPT_STATUS if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data); #endif + + if (payload) { + XdrvRulesProcess(); // Allow rule processing on single Status command only + } + mqtt_data[0] = '\0'; } From bdc0934cb82f0d2a1df8f5ca7493e070550950ef Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 12:59:13 +0200 Subject: [PATCH 18/30] Fix init of Energy Offset after power cycle Fix init of Energy Offset after power cycle (#8356) --- tasmota/xdrv_03_energy.ino | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino index bb74b991d..4210cea1c 100644 --- a/tasmota/xdrv_03_energy.ino +++ b/tasmota/xdrv_03_energy.ino @@ -96,6 +96,7 @@ struct ENERGY { uint8_t phase_count = 1; // Number of phases active bool voltage_common = false; // Use single voltage and frequency + bool kWhtoday_offset_init = false; bool voltage_available = true; // Enable if voltage is measured bool current_available = true; // Enable if current is measured @@ -223,6 +224,12 @@ void Energy200ms(void) XnrgCall(FUNC_ENERGY_EVERY_SECOND); if (RtcTime.valid) { + + if (!Energy.kWhtoday_offset_init && (RtcTime.day_of_year == Settings.energy_kWhdoy)) { + Energy.kWhtoday_offset = Settings.energy_kWhtoday; + Energy.kWhtoday_offset_init = true; + } + if (LocalTime() == Midnight()) { Settings.energy_kWhyesterday = RtcSettings.energy_kWhtoday; @@ -827,14 +834,11 @@ void EnergySnsInit(void) XnrgCall(FUNC_INIT); if (energy_flg) { - if (RtcSettingsValid()) { + Energy.kWhtoday_offset = 0; + // Do not use at Power On as Rtc was invalid (but has been restored from Settings already) + if ((ResetReason() != REASON_DEFAULT_RST) && RtcSettingsValid()) { Energy.kWhtoday_offset = RtcSettings.energy_kWhtoday; - } - else if (RtcTime.day_of_year == Settings.energy_kWhdoy) { - Energy.kWhtoday_offset = Settings.energy_kWhtoday; - } - else { - Energy.kWhtoday_offset = 0; + Energy.kWhtoday_offset_init = true; } Energy.kWhtoday = 0; Energy.kWhtoday_delta = 0; From ec045445e6456802967a7de8e40686bf6a775242 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 14:39:13 +0200 Subject: [PATCH 19/30] Enable rule compression in my_user_config.h --- RELEASENOTES.md | 3 ++- tasmota/CHANGELOG.md | 3 ++- tasmota/my_user_config.h | 4 ++-- tasmota/xdrv_10_rules.ino | 6 +++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 24e65e4b0..cd4f55cf1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -82,6 +82,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add command ``SetOption90 1`` to disable non-json MQTT messages (#8044) - Add command ``SetOption91 1`` to enable fading at startup / power on - Add command ``SetOption92 1`` to set PWM Mode from regular PWM to ColorTemp control (Xiaomi Philips ...) +- Add command ``SetOption93 1`` to control caching of compressed rules - Add command ``Sensor10 0/1/2`` to control BH1750 resolution - 0 = High (default), 1 = High2, 2 = Low (#8016) - Add command ``Sensor10 31..254`` to control BH1750 measurement time which defaults to 69 (#8016) - Add command ``Sensor18 0..32000`` to control PMS5003 sensor interval to extend lifetime by Gene Ruebsamen (#8128) @@ -103,5 +104,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt -- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules +- Add automatic compression of Rules to achieve ~60% compression by Stefan Hadinger - Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index a54809a68..aad27a856 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -6,7 +6,8 @@ - Add support for analog anemometer by Matteo Albinola (#8283) - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) -- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules +- Add automatic compression of Rules to achieve ~60% compression by Stefan Hadinger +- Add command ``SetOption93 1`` to control caching of compressed rules - Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index ce93a6eed..9af23d0b8 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -395,9 +395,9 @@ // #define USE_PING // Enable Ping command (+2k code) // -- Rules or Script ---------------------------- -// Select none or only one of the below defines +// 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.8k 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 f2f01a8db..33f61a68c 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -47,7 +47,7 @@ * on button1#state do publish cmnd/ring2/power %value% endon on button2#state do publish cmnd/strip1/power %value% endon * on switch1#state do power2 %value% endon * on analog#a0div10 do publish cmnd/ring2/dimmer %value% endon - * on root#loadavg<50 do power 2 endon + * on loadavg<50 do power 2 endon * * Notes: * Spaces after , around and before are mandatory @@ -203,7 +203,7 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; * Rule[x][0] = 0, if firmware is downgraded, the rule will be considered as empty * * The second byte contains the size of uncompressed rule in 8-bytes blocks (i.e. (len+7)/8 ) - * Maximum rule size si 2KB (2048 bytes per rule), although there is little chances compression ratio will go down to 75% + * Maximum rule size is 2KB (2048 bytes per rule), although there is little chances compression ratio will go down to 75% * Rule[x][1] = size uncompressed in dwords. If zero, the rule is empty. * * The remaining bytes contain the compressed rule, NULL terminated @@ -1987,7 +1987,7 @@ void CmndRule(void) } int32_t res = SetRule(index - 1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, append); if (res < 0) { - AddLog_P2(LOG_LEVEL_ERROR, PSTR("RUL: not enough space")); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("RUL: Not enough space")); } } Rules.triggers[index -1] = 0; // Reset once flag From 93a4700552aa8cd3c4cdcfcfb5008fe4123fa1b2 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 14:58:38 +0200 Subject: [PATCH 20/30] Cleanup default defines --- tasmota/my_user_config.h | 2 +- tasmota/support_features.ino | 2 +- tasmota/tasmota_configurations.h | 29 ++++++++++++++++++++--------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 9af23d0b8..60c4f87fb 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -670,7 +670,7 @@ //#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code) // -- Thermostat control ---------------------------- -//#define USE_THERMOSTAT // Add support for Thermostat +//#define USE_THERMOSTAT // Add support for Thermostat #define THERMOSTAT_CONTROLLER_OUTPUTS 1 // Number of outputs to be controlled independently #define THERMOSTAT_SENSOR_NAME "DS18B20" // Name of the local sensor to be used #define THERMOSTAT_RELAY_NUMBER 1 // Default output relay number for the first controller (+i for following ones) diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index 33b771545..02a04a962 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -477,7 +477,7 @@ void GetFeatures(void) #ifdef USE_EXS_DIMMER feature5 |= 0x00008000; // xdrv_30_exs_dimmer.ino #endif -#ifdef USE_ARDUINO_SLAVE +#ifdef USE_TASMOTA_SLAVE feature5 |= 0x00010000; // xdrv_31_arduino_slave.ino #endif #ifdef USE_HIH6 diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index a3494a6c7..f0444efbb 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -143,12 +143,15 @@ #define MP3_VOLUME 10 // Set the startup volume on init, the range can be 0..30(max) //#define USE_AZ7798 // Add support for AZ-Instrument 7798 CO2 datalogger #define USE_PN532_HSU // Add support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) +//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP #define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8) #define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) //#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #define USE_HM10 // (ESP8266 only) Add support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) //#define USE_MI_ESP32 // (ESP32 only) Add support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #define USE_HRXL // Add support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) +//#define USE_TASMOTA_SLAVE // Add support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) +//#define USE_OPENTHERM // Add support for OpenTherm (+15k code) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) @@ -168,22 +171,20 @@ #define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem, 48 iram) #define USE_IR_RECEIVE // Support for IR receiver (+5k5 code, 264 iram) -//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP - #define USE_SR04 // Add support for HC-SR04 ultrasonic devices (+1k code) #define USE_TM1638 // Add support for TM1638 switches copying Switch1 .. Switch8 (+1k code) #define USE_HX711 // Add support for HX711 load cell (+1k5 code) //#define USE_HX711_GUI // Add optional web GUI to HX711 as scale (+1k8 code) //#define USE_TX20_WIND_SENSOR // Add support for La Crosse TX20 anemometer (+2k6/0k8 code) //#define USE_TX23_WIND_SENSOR // Add support for La Crosse TX23 anemometer (+2k7/1k code) +//#define USE_WINDMETER // Add support for analog anemometer (+2k2 code) #define USE_RC_SWITCH // Add support for RF transceiver using library RcSwitch (+2k7 code, 460 iram) #define USE_RF_SENSOR // Add support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) // #define USE_THEO_V2 // Add support for decoding Theo V2 sensors as documented on https://sidweb.nl using 434MHz RF sensor receiver (+1k4 code) #define USE_ALECTO_V2 // Add support for decoding Alecto V2 sensors like ACH2010, WS3000 and DKW2012 using 868MHz RF sensor receiver (+1k7 code) #define USE_HRE // Add support for Badger HR-E Water Meter (+1k4 code) //#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code) -//#define USE_ARDUINO_SLAVE // Add support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) -//#define USE_WINDMETER // Add support for analog anemometer +//#define USE_THERMOSTAT // Add support for Thermostat #undef DEBUG_THEO // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code #endif // FIRMWARE_SENSORS @@ -366,12 +367,15 @@ #undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop #undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) +#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) #undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) +#undef USE_TASMOTA_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) +#undef USE_OPENTHERM // Disable support for OpenTherm (+15k code) //#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor #undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI @@ -381,11 +385,12 @@ #undef USE_HX711 // Disable support for HX711 load cell #undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer #undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer +#undef USE_WINDMETER // Disable support for analog anemometer (+2k2 code) #undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch #undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code) #undef USE_A4988_STEPPER // Disable support for A4988_Stepper -#undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) +#undef USE_THERMOSTAT // Disable support for Thermostat #undef DEBUG_THEO // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code #endif // FIRMWARE_IR @@ -467,12 +472,15 @@ #undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop #undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) +#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) #undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) +#undef USE_TASMOTA_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) +#undef USE_OPENTHERM // Disable support for OpenTherm (+15k code) //#undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_PZEM004T // Disable PZEM004T energy sensor @@ -491,18 +499,17 @@ #undef USE_MAX31865 // Disable support for MAX31865 RTD sensors using softSPI #undef USE_IR_REMOTE // Disable IR driver -#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP - #undef USE_SR04 // Disable support for for HC-SR04 ultrasonic devices #undef USE_TM1638 // Disable support for TM1638 switches copying Switch1 .. Switch8 #undef USE_HX711 // Disable support for HX711 load cell #undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer #undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer +#undef USE_WINDMETER // Disable support for analog anemometer (+2k2 code) #undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch #undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code) #undef USE_A4988_STEPPER // Disable support for A4988_Stepper -#undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) +#undef USE_THERMOSTAT // Disable support for Thermostat #undef DEBUG_THEO // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code #endif // FIRMWARE_LITE @@ -590,12 +597,15 @@ #undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop #undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) +#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) #undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) +#undef USE_TASMOTA_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) +#undef USE_OPENTHERM // Disable support for OpenTherm (+15k code) #undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_PZEM004T // Disable PZEM004T energy sensor @@ -618,11 +628,12 @@ #undef USE_HX711 // Disable support for HX711 load cell #undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer #undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer +#undef USE_WINDMETER // Disable support for analog anemometer (+2k2 code) #undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch #undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code) #undef USE_A4988_STEPPER // Disable support for A4988_Stepper -#undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) +#undef USE_THERMOSTAT // Disable support for Thermostat #undef DEBUG_THEO // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code #endif // FIRMWARE_MINIMAL From 1b682d3564ef56b2406cf8fd83f44dc2600f224e Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 10 May 2020 15:24:10 +0200 Subject: [PATCH 21/30] Add rule length, truncates rules too long and add escape JSON --- tasmota/support.ino | 59 +++++++++++++++++++++++++++++++++++++++ tasmota/xdrv_10_rules.ino | 13 +++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index 45c6b0e7a..5d7d06624 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -1863,3 +1863,62 @@ void AddLogBufferSize(uint32_t loglevel, uint8_t *buffer, uint32_t count, uint32 } AddLog(loglevel); } + +/*********************************************************************************************\ + * JSON parsing +\*********************************************************************************************/ + +// does the character needs to be escaped, and if so with which character +char escapeJSONChar(char c) { + if ((c == '\"') || (c == '\\')) { + return c; + } + if (c == '\n') { return 'n'; } + if (c == '\t') { return 't'; } + if (c == '\r') { return 'r'; } + if (c == '\f') { return 'f'; } + if (c == '\b') { return 'b'; } + return 0; +} + +String escapeJSONString(const char *str) { + String r(""); + if (nullptr == str) { return r; } + + bool needs_escape = false; + size_t len_out = 1; + const char * c = str; + + while (*c) { + if (escapeJSONChar(*c)) { + len_out++; + needs_escape = true; + } + c++; + len_out++; + } + + if (needs_escape) { + // we need to escape some chars + // allocate target buffer + r.reserve(len_out); + c = str; + char *d = r.begin(); + while (*c) { + char c2 = escapeJSONChar(*c); + if (c2) { + c++; + *d++ = '\\'; + *d++ = c2; + } else { + *d++ = *c++; + } + } + *d = 0; // add NULL terminator + r = (char*) r.begin(); // assign the buffer to the string + } else { + r = str; + } + + return r; +} \ No newline at end of file diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index 33f61a68c..922014e49 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -1992,12 +1992,21 @@ void CmndRule(void) } Rules.triggers[index -1] = 0; // Reset once flag } + String rule = GetRule(index - 1); + size_t rule_len = rule.length(); + if (rule_len >= MAX_RULE_SIZE) { + // we need to split the rule in chunks + rule = rule.substring(0, MAX_RULE_SIZE); + rule += F("..."); + } // snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"), // XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), // GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]); - snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"), + snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Length\":%d,\"Free\":%d,\"Rules\":\"%s\"}"), XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), - GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[0]) - GetRuleLenStorage(index - 1), GetRule(index - 1).c_str()); + GetStateText(bitRead(Settings.rule_stop, index -1)), + rule_len, MAX_RULE_SIZE - GetRuleLenStorage(index - 1), + escapeJSONString(rule.c_str()).c_str()); } } From 9be73adb66bf61778aee26bdcf1fc52354c63108 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Sun, 10 May 2020 17:49:30 +0200 Subject: [PATCH 22/30] fix epaper driver for esp32 --- lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd2in9.cpp | 10 ++++++++-- lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd4in2.cpp | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd2in9.cpp b/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd2in9.cpp index 686b0391e..90e257483 100644 --- a/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd2in9.cpp +++ b/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd2in9.cpp @@ -364,8 +364,12 @@ const unsigned char lut_partial_update[] = #define PIN_OUT_SET 0x60000304 #define PIN_OUT_CLEAR 0x60000308 -#define PWRITE xdigitalWrite - +#ifdef ESP32 +#define SSPI_USEANYPIN 1 +#define PWRITE digitalWrite +#else +#define PWRITE ydigitalWrite +#endif #ifndef SSPI_USEANYPIN // uses about 2.75 usecs, 365 kb /sec @@ -388,6 +392,7 @@ void ICACHE_RAM_ATTR Epd::fastSPIwrite(uint8_t d,uint8_t dc) { } #else +#ifndef ESP32 extern void ICACHE_RAM_ATTR xdigitalWrite(uint8_t pin, uint8_t val) { //stopWaveform(pin); if(pin < 16){ @@ -398,6 +403,7 @@ extern void ICACHE_RAM_ATTR xdigitalWrite(uint8_t pin, uint8_t val) { else GP16O &= ~1; } } +#endif // about 13 us => 76 kb / sec // can use any pin diff --git a/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd4in2.cpp b/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd4in2.cpp index 8c8d037c5..c65f4ffd5 100755 --- a/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd4in2.cpp +++ b/lib/esp-epaper-29-ws-20171230-gemu-1.1/src/epd4in2.cpp @@ -502,12 +502,15 @@ const unsigned char lut_wb_quick[] PROGMEM = 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; - - #define PIN_OUT_SET 0x60000304 #define PIN_OUT_CLEAR 0x60000308 +#ifdef ESP32 +#define SSPI_USEANYPIN 1 +#define PWRITE digitalWrite +#else #define PWRITE ydigitalWrite +#endif #ifndef SSPI_USEANYPIN // uses about 2.75 usecs, 365 kb /sec @@ -530,6 +533,7 @@ void ICACHE_RAM_ATTR Epd42::fastSPIwrite(uint8_t d,uint8_t dc) { } #else +#ifndef ESP32 extern void ICACHE_RAM_ATTR ydigitalWrite(uint8_t pin, uint8_t val) { //stopWaveform(pin); if(pin < 16){ @@ -540,6 +544,7 @@ extern void ICACHE_RAM_ATTR ydigitalWrite(uint8_t pin, uint8_t val) { else GP16O &= ~1; } } +#endif // about 13 us => 76 kb / sec // can use any pin void Epd42::fastSPIwrite(uint8_t d,uint8_t dc) { From 9b0aa51515176d9dc1b82731eaf9afa3cb56f134 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Sun, 10 May 2020 17:51:22 +0200 Subject: [PATCH 23/30] scripter fix create task --- tasmota/xdrv_10_scripter.ino | 53 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 38c8f158f..0b9bfa200 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -891,6 +891,16 @@ if (hsv.S == 0) { #endif //#endif + +#ifdef ESP32 +#include "esp_pm.h" +#include "esp_clk.h" +uint32_t switch_freq(uint32_t mhz) { + setCpuFrequencyMhz(mhz); //Set CPU clock to 80MHz fo example + return getCpuFrequencyMhz(); //Get CPU clock +} +#endif // ESP32 + // vtype => ff=nothing found, fe=constant number,fd = constant string else bit 7 => 80 = string, 0 = number // no flash strings here for performance reasons!!! char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,JsonObject *jo) { @@ -1879,6 +1889,19 @@ chknext: len=0; goto strexit; } +#ifdef ESP32 + if (!strncmp(vname,"sf(",3)) { + lp+=2; + lp=GetNumericResult(lp,OPER_EQU,&fvar,0); + if (fvar<80) fvar=80; + if (fvar>240) fvar=240; + setCpuFrequencyMhz(fvar); + fvar=getCpuFrequencyMhz(); + lp++; + len=0; + goto exit; + } +#endif #if defined(USE_TIMERS) && defined(USE_SUNRISE) if (!strncmp(vname,"sunrise",7)) { fvar=SunMinutes(0); @@ -2093,7 +2116,7 @@ chknext: len=0; goto exit; } -#endif +#endif //ESP32, USE_WEBCAM if (!strncmp(vname,"wday",4)) { fvar=RtcTime.day_of_week; goto exit; @@ -2692,11 +2715,13 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { return -99; } - DynamicJsonBuffer jsonBuffer; // on heap - JsonObject &jobj=jsonBuffer.parseObject(js); - JsonObject *jo; - if (js) jo=&jobj; - else jo=0; + JsonObject *jo=0; + + if (js) { + DynamicJsonBuffer jsonBuffer; // on heap + JsonObject &jobj=jsonBuffer.parseObject(js); + jo=&jobj; + } char *lp=glob_script_mem.scriptptr; @@ -5119,6 +5144,8 @@ bool RulesProcessEvent(char *json_event) { #ifdef USE_SCRIPT_TASK uint16_t task_timer1; uint16_t task_timer2; +TaskHandle_t task_t1; +TaskHandle_t task_t2; void script_task1(void *arg) { while (1) { @@ -5143,14 +5170,16 @@ void script_task2(void *arg) { uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core) { //return 0; - BaseType_t res=0; - if (core>1) {core = 1;} + BaseType_t res = 0; + if (core > 1) { core = 1; } if (num == 1) { - res = xTaskCreatePinnedToCore(script_task1, "T 1", STASK_STACK, NULL, STASK_PRIO, NULL, core); - task_timer1=time; + if (task_t1) { vTaskDelete(task_t1); } + res = xTaskCreatePinnedToCore(script_task1, "T1", STASK_STACK, NULL, STASK_PRIO, &task_t1, core); + task_timer1 = time; } else { - res = xTaskCreatePinnedToCore(script_task2, "T 2", STASK_STACK, NULL, STASK_PRIO, NULL, core); - task_timer2=time; + if (task_t2) { vTaskDelete(task_t2); } + res = xTaskCreatePinnedToCore(script_task2, "T2", STASK_STACK, NULL, STASK_PRIO, &task_t2, core); + task_timer2 = time; } return res; } From 4d84a02b5fe9b39d131db5d1eb0fe5d3218ea370 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 17:52:28 +0200 Subject: [PATCH 24/30] Add /snapshot.jpg link to webcam --- tasmota/xdrv_81_webcam.ino | 52 +++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/tasmota/xdrv_81_webcam.ino b/tasmota/xdrv_81_webcam.ino index 15c2dd4ba..95ec03cc4 100644 --- a/tasmota/xdrv_81_webcam.ino +++ b/tasmota/xdrv_81_webcam.ino @@ -354,8 +354,8 @@ uint32_t wc_get_jpeg(uint8_t **buff) { _jpg_buf_len = wc_fb->len; _jpg_buf = wc_fb->buf; } - esp_camera_fb_return(wc_fb); - *buff = _jpg_buf; + esp_camera_fb_return(wc_fb); // This frees the buffer + *buff = _jpg_buf; // Buffer has been freed so this will cause exceptions return _jpg_buf_len; } @@ -447,7 +447,7 @@ void HandleImage(void) { len = wc_get_jpeg(&buff); if (len) { client.write(buff,len); - free(buff); + free(buff); // Buffer has been freed already in wc_get_jpeg so this will cause exceptions } } else { bnum--; @@ -475,6 +475,50 @@ void handleMjpeg(void) { } } +void HandleImageTheo(void) { + if (!HttpCheckPriviledgedAccess(true)) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP "Capture image")); + + if (Settings.webcam_config.stream) { + if (!CamServer) { + WcStreamControl(); + } + } + + camera_fb_t *wc_fb; + wc_fb = esp_camera_fb_get(); // Acquire frame + if (!wc_fb) { + AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Frame buffer could not be acquired")); + return; + } + + size_t _jpg_buf_len = 0; + uint8_t * _jpg_buf = NULL; + if (wc_fb->format != PIXFORMAT_JPEG) { + bool jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len); + if (!jpeg_converted) { + _jpg_buf_len = wc_fb->len; + _jpg_buf = wc_fb->buf; + } + } else { + _jpg_buf_len = wc_fb->len; + _jpg_buf = wc_fb->buf; + } + + if (_jpg_buf_len) { + Webserver->client().flush(); + WSHeaderSend(); + Webserver->sendHeader(F("Content-disposition"), F("inline; filename=cap.jpg")); + Webserver->send_P(200, "image/jpeg", (char *)_jpg_buf, _jpg_buf_len); + Webserver->client().stop(); + } + + esp_camera_fb_return(wc_fb); // Free frame buffer + + AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Image sent")); +} + #ifdef USE_FACE_DETECT static mtmn_config_t mtmn_config = {0}; @@ -812,6 +856,8 @@ void wc_loop(void) { void wc_pic_setup(void) { Webserver->on("/wc.jpg", HandleImage); Webserver->on("/wc.mjpeg", HandleImage); + + Webserver->on("/snapshot.jpg", HandleImageTheo); } /* From 39a92c276780940c0d09599d2967cccd7dce3bb5 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Sun, 10 May 2020 18:00:37 +0200 Subject: [PATCH 25/30] Update xdrv_10_scripter.ino --- tasmota/xdrv_10_scripter.ino | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 0b9bfa200..d16c0bd31 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -891,16 +891,6 @@ if (hsv.S == 0) { #endif //#endif - -#ifdef ESP32 -#include "esp_pm.h" -#include "esp_clk.h" -uint32_t switch_freq(uint32_t mhz) { - setCpuFrequencyMhz(mhz); //Set CPU clock to 80MHz fo example - return getCpuFrequencyMhz(); //Get CPU clock -} -#endif // ESP32 - // vtype => ff=nothing found, fe=constant number,fd = constant string else bit 7 => 80 = string, 0 = number // no flash strings here for performance reasons!!! char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,JsonObject *jo) { From e1def09a1b179ff09ed2a75ec115f8c61c89da12 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 10 May 2020 19:10:00 +0200 Subject: [PATCH 26/30] Change Philips Hue emulation now exposes modelId and manufacturerId --- tasmota/CHANGELOG.md | 1 + tasmota/xdrv_20_hue.ino | 14 +++++++++----- tasmota/xdrv_23_zigbee_2_devices.ino | 10 ++++++++++ tasmota/xdrv_23_zigbee_3_hue.ino | 9 +++++++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index aad27a856..dcdcf2840 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -14,6 +14,7 @@ - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz - Change PWM updated to the latest version of Arduino PR #7231 +- Change Philips Hue emulation now exposes modelId and manufacturerId ### 8.2.0.5 20200425 diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino index 201491d09..122166010 100644 --- a/tasmota/xdrv_20_hue.ino +++ b/tasmota/xdrv_20_hue.ino @@ -144,9 +144,9 @@ const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM = const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM = ",\"type\":\"Extended color light\"," "\"name\":\"%s\"," - "\"modelid\":\"LCT007\"," - "\"uniqueid\":\"%s\"," - "\"swversion\":\"5.50.1.19085\"}"; + "\"modelid\":\"%s\"," + "\"manufacturername\":\"%s\"," + "\"uniqueid\":\"%s\"}"; const char HUE_GROUP0_STATUS_JSON[] PROGMEM = "{\"name\":\"Group 0\"," "\"lights\":[{l1]," @@ -358,7 +358,7 @@ bool HueActive(uint8_t device) { void HueLightStatus2(uint8_t device, String *response) { - const size_t buf_size = 192; + const size_t buf_size = 300; char * buf = (char*) malloc(buf_size); const size_t max_name_len = 32; char fname[max_name_len + 1]; @@ -376,7 +376,11 @@ void HueLightStatus2(uint8_t device, String *response) } fname[fname_len] = 0x00; } - snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON2, fname, GetHueDeviceId(device).c_str()); + snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON2, + escapeJSONString(fname).c_str(), + escapeJSONString(Settings.user_template_name).c_str(), + PSTR("Tasmota"), + GetHueDeviceId(device).c_str()); *response += buf; free(buf); } diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index 23970042e..76413e23e 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -127,6 +127,7 @@ public: void setFriendlyName(uint16_t shortaddr, const char * str); const char * getFriendlyName(uint16_t shortaddr) const; const char * getModelId(uint16_t shortaddr) const; + const char * getManufacturerId(uint16_t shortaddr) const; void setReachable(uint16_t shortaddr, bool reachable); // get next sequence number for (increment at each all) @@ -589,6 +590,15 @@ const char * Z_Devices::getModelId(uint16_t shortaddr) const { return nullptr; } +const char * Z_Devices::getManufacturerId(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + return device.manufacturerId; + } + return nullptr; +} + void Z_Devices::setReachable(uint16_t shortaddr, bool reachable) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found diff --git a/tasmota/xdrv_23_zigbee_3_hue.ino b/tasmota/xdrv_23_zigbee_3_hue.ino index 87b7d39da..d5c4b1f08 100644 --- a/tasmota/xdrv_23_zigbee_3_hue.ino +++ b/tasmota/xdrv_23_zigbee_3_hue.ino @@ -75,16 +75,21 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri void HueLightStatus2Zigbee(uint16_t shortaddr, String *response) { - const size_t buf_size = 192; + const size_t buf_size = 300; char * buf = (char*) malloc(buf_size); const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr); + const char * modelId = zigbee_devices.getModelId(shortaddr); + const char * manufacturerId = zigbee_devices.getManufacturerId(shortaddr); char shortaddrname[8]; snprintf_P(shortaddrname, sizeof(shortaddrname), PSTR("0x%04X"), shortaddr); snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON2, - (friendlyName) ? friendlyName : shortaddrname, + (friendlyName) ? escapeJSONString(friendlyName).c_str() : shortaddrname, + (modelId) ? escapeJSONString(modelId).c_str() : PSTR("Unknown"), + (manufacturerId) ? escapeJSONString(manufacturerId).c_str() : PSTR("Tasmota"), GetHueDeviceId(shortaddr).c_str()); + *response += buf; free(buf); } From 5062dc2c332015a95c7adfb352e33520246b019d Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Sun, 10 May 2020 23:00:57 +0200 Subject: [PATCH 27/30] Optimizations, correction of comments and update of readme --- README.md | 1 + tasmota/my_user_config.h | 5 +- tasmota/xdrv_39_thermostat.ino | 131 +++++++++++++++------------------ 3 files changed, 61 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 148f00818..4fa7d6e4a 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,7 @@ People helping to keep the show on the road: - Christian Staars for NRF24L01 and HM-10 Bluetooth sensor support - Paul Diem for UDP Group communication support - Jörg Schüler-Maroldt for his initial ESP32 port +- Javier Arigita for his thermostat driver - Many more providing Tips, Wips, Pocs, PRs and Donations ## License diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 60c4f87fb..2e3eb4a5f 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -675,13 +675,12 @@ #define THERMOSTAT_SENSOR_NAME "DS18B20" // Name of the local sensor to be used #define THERMOSTAT_RELAY_NUMBER 1 // Default output relay number for the first controller (+i for following ones) #define THERMOSTAT_SWITCH_NUMBER 1 // Default input switch number for the first controller (+i for following ones) - #define THERMOSTAT_TIME_ALLOW_RAMPUP 300 // Default time in seconds after last target update to allow ramp-up controller phase in minutes + #define THERMOSTAT_TIME_ALLOW_RAMPUP 300 // Default time after last target update to allow ramp-up controller phase in minutes #define THERMOSTAT_TIME_RAMPUP_MAX 960 // Default time maximum ramp-up controller duration in minutes - #define THERMOSTAT_TIME_RAMPUP_CYCLE 1800 // Default time ramp-up cycle in seconds + #define THERMOSTAT_TIME_RAMPUP_CYCLE 30 // Default time ramp-up cycle in minutes #define THERMOSTAT_TIME_SENS_LOST 30 // Maximum time w/o sensor update to set it as lost in minutes #define THERMOSTAT_TEMP_SENS_NUMBER 1 // Default temperature sensor number #define THERMOSTAT_TIME_MANUAL_TO_AUTO 60 // Default time without input switch active to change from manual to automatic in minutes - #define THERMOSTAT_TIME_ON_LIMIT 120 // Default maximum time with output active in minutes #define THERMOSTAT_TIME_RESET 12000 // Default reset time of the PI controller in seconds #define THERMOSTAT_TIME_PI_CYCLE 30 // Default cycle time for the thermostat controller in minutes #define THERMOSTAT_TIME_MAX_ACTION 20 // Default maximum thermostat time per cycle in minutes diff --git a/tasmota/xdrv_39_thermostat.ino b/tasmota/xdrv_39_thermostat.ino index 0ccf777ad..5c9fc2739 100644 --- a/tasmota/xdrv_39_thermostat.ino +++ b/tasmota/xdrv_39_thermostat.ino @@ -134,18 +134,18 @@ const char kThermostatCommands[] PROGMEM = "|" D_CMND_THERMOSTATMODESET "|" D_CM D_CMND_TEMPFROSTPROTECTSET "|" D_CMND_CONTROLLERMODESET "|" D_CMND_INPUTSWITCHSET "|" D_CMND_INPUTSWITCHUSE "|" D_CMND_OUTPUTRELAYSET "|" D_CMND_TIMEALLOWRAMPUPSET "|" D_CMND_TEMPFORMATSET "|" D_CMND_TEMPMEASUREDSET "|" D_CMND_TEMPTARGETSET "|" D_CMND_TEMPMEASUREDGRDREAD "|" D_CMND_SENSORINPUTSET "|" D_CMND_STATEEMERGENCYSET "|" - D_CMND_TIMEMANUALTOAUTOSET "|" D_CMND_TIMEONLIMITSET "|" D_CMND_PROPBANDSET "|" D_CMND_TIMERESETSET "|" - D_CMND_TIMEPICYCLESET "|" D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|" - D_CMND_TIMEMINACTIONSET "|" D_CMND_TIMEMINTURNOFFACTIONSET "|" D_CMND_TEMPRUPDELTINSET "|" D_CMND_TEMPRUPDELTOUTSET "|" - D_CMND_TIMERAMPUPMAXSET "|" D_CMND_TIMERAMPUPCYCLESET "|" D_CMND_TEMPRAMPUPPIACCERRSET "|" D_CMND_TIMEPIPROPORTREAD "|" - D_CMND_TIMEPIINTEGRREAD "|" D_CMND_TIMESENSLOSTSET "|" D_CMND_DIAGNOSTICMODESET; + 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; void (* const ThermostatCommand[])(void) PROGMEM = { &CmndThermostatModeSet, &CmndClimateModeSet, &CmndTempFrostProtectSet, &CmndControllerModeSet, &CmndInputSwitchSet, &CmndInputSwitchUse, &CmndOutputRelaySet, &CmndTimeAllowRampupSet, &CmndTempFormatSet, &CmndTempMeasuredSet, &CmndTempTargetSet, &CmndTempMeasuredGrdRead, &CmndSensorInputSet, &CmndStateEmergencySet, &CmndTimeManualToAutoSet, - &CmndTimeOnLimitSet, &CmndPropBandSet, &CmndTimeResetSet, &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, - &CmndTempHystSet, &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet, + &CmndPropBandSet, &CmndTimeResetSet, &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, &CmndTempHystSet, + &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet, &CmndTempRupDeltOutSet, &CmndTimeRampupMaxSet, &CmndTimeRampupCycleSet, &CmndTempRampupPiAccErrSet, &CmndTimePiProportRead, &CmndTimePiIntegrRead, &CmndTimeSensLostSet, &CmndDiagnosticModeSet }; @@ -183,16 +183,15 @@ struct THERMOSTAT { int16_t temp_rampup_start = 0; // Temperature at start of ramp-up controller in tenths of degrees celsius int16_t temp_rampup_cycle = 0; // Temperature set at the beginning of each ramp-up cycle in tenths of degrees uint16_t time_rampup_max = THERMOSTAT_TIME_RAMPUP_MAX; // Time maximum ramp-up controller duration in minutes - uint16_t time_rampup_cycle = THERMOSTAT_TIME_RAMPUP_CYCLE; // Time ramp-up cycle in seconds + uint16_t time_rampup_cycle = THERMOSTAT_TIME_RAMPUP_CYCLE; // Time ramp-up cycle in minutes uint16_t time_allow_rampup = THERMOSTAT_TIME_ALLOW_RAMPUP; // Time in minutes after last target update to allow ramp-up controller phase - uint16_t time_sens_lost = THERMOSTAT_TIME_SENS_LOST; // Maximum time w/o sensor update to set it as lost + uint16_t time_sens_lost = THERMOSTAT_TIME_SENS_LOST; // Maximum time w/o sensor update to set it as lost in minutes uint16_t time_manual_to_auto = THERMOSTAT_TIME_MANUAL_TO_AUTO; // Time without input switch active to change from manual to automatic in minutes - uint16_t time_on_limit = THERMOSTAT_TIME_ON_LIMIT; // Maximum time with output active in minutes - uint16_t time_pi_cycle = THERMOSTAT_TIME_PI_CYCLE; // Cycle time for the thermostat controller in seconds uint32_t time_reset = THERMOSTAT_TIME_RESET; // Reset time of the PI controller in seconds + uint16_t time_pi_cycle = THERMOSTAT_TIME_PI_CYCLE; // Cycle time for the thermostat controller in minutes uint16_t time_max_action = THERMOSTAT_TIME_MAX_ACTION; // Maximum thermostat time per cycle in minutes uint16_t time_min_action = THERMOSTAT_TIME_MIN_ACTION; // Minimum thermostat time per cycle in minutes - uint16_t time_min_turnoff_action = THERMOSTAT_TIME_MIN_TURNOFF_ACTION; // Minimum turnoff time in minutes, below it the thermostat will be held on + uint16_t time_min_turnoff_action = THERMOSTAT_TIME_MIN_TURNOFF_ACTION; // Minimum turnoff time in minutes, below it the thermostat will stay on uint8_t temp_reset_anti_windup = THERMOSTAT_TEMP_RESET_ANTI_WINDUP; // Range where reset antiwindup is disabled, in tenths of degrees celsius int8_t temp_hysteresis = THERMOSTAT_TEMP_HYSTERESIS; // Range hysteresis for temperature PI controller, in tenths of degrees celsius uint8_t temp_frost_protect = THERMOSTAT_TEMP_FROST_PROTECT; // Minimum temperature for frost protection, in tenths of degrees celsius @@ -605,7 +604,7 @@ void ThermostatCalculatePI(uint8_t ctr_output) // Antiwindup of the integrator // If integral calculation is bigger than cycle time, adjust result - // to the cycle time and error will not be cummulated]] + // to the cycle time and error will not be cummulated if (Thermostat[ctr_output].time_integral_pi > ((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60)) { Thermostat[ctr_output].time_integral_pi = ((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60); } @@ -616,7 +615,7 @@ void ThermostatCalculatePI(uint8_t ctr_output) // Antiwindup of the output // If result is bigger than cycle time, the result will be adjusted - // to the cylce time minus safety time and error will not be cummulated]] + // to the cylce time minus safety time and error will not be cummulated if (Thermostat[ctr_output].time_total_pi >= ((int32_t)Thermostat[ctr_output].time_pi_cycle * 60)) { // Limit to cycle time //at least switch down a minimum time Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_pi_cycle * 60); @@ -651,13 +650,13 @@ void ThermostatCalculatePI(uint8_t ctr_output) } // Minimum action limiter - // If result is less than the minimum action time, adjust to minimum value]] + // If result is less than the minimum action time, adjust to minimum value if ((Thermostat[ctr_output].time_total_pi <= abs(((uint32_t)Thermostat[ctr_output].time_min_action * 60))) && (Thermostat[ctr_output].time_total_pi != 0)) { Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_min_action * 60); } // Maximum action limiter - // If result is more than the maximum action time, adjust to maximum value]] + // If result is more than the maximum action time, adjust to maximum value else if (Thermostat[ctr_output].time_total_pi > abs(((int32_t)Thermostat[ctr_output].time_max_action * 60))) { Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_max_action * 60); } @@ -747,7 +746,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output) } // Calculate absolute gradient since start of ramp-up (considering deadtime) in thousandths of º/hour Thermostat[ctr_output].temp_rampup_meas_gradient = (int32_t)((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_in_rampup); - Thermostat[ctr_output].time_rampup_nextcycle = uptime + (uint32_t)Thermostat[ctr_output].time_rampup_cycle; + Thermostat[ctr_output].time_rampup_nextcycle = uptime + ((uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60); // Set auxiliary variables Thermostat[ctr_output].temp_rampup_cycle = Thermostat[ctr_output].temp_measured; Thermostat[ctr_output].time_ctr_changepoint = uptime + (60 * (uint32_t)Thermostat[ctr_output].time_rampup_max); @@ -758,7 +757,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output) // Calculate temp. gradient in º/hour and set again time_rampup_nextcycle and temp_rampup_cycle // temp_rampup_meas_gradient = ((3600 * temp_delta_rampup) / (os.time() - time_rampup_nextcycle)) temp_delta_rampup = Thermostat[ctr_output].temp_measured - Thermostat[ctr_output].temp_rampup_cycle; - uint32_t time_total_rampup = (uint32_t)Thermostat[ctr_output].time_rampup_cycle * Thermostat[ctr_output].counter_rampup_cycles; + uint32_t time_total_rampup = (uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60 * Thermostat[ctr_output].counter_rampup_cycles; // Translate into gradient per hour (thousandths of ° per hour) Thermostat[ctr_output].temp_rampup_meas_gradient = int32_t((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_total_rampup); if ( ((Thermostat[ctr_output].temp_rampup_meas_gradient > 0) @@ -776,7 +775,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output) // y = (((y2-y1)/(x2-x1))*(x-x1)) + y1 Thermostat[ctr_output].temp_rampup_output_off = (int16_t)(((int32_t)temp_delta_rampup * (int32_t)(Thermostat[ctr_output].time_ctr_changepoint - (uptime - (time_total_rampup)))) / (int32_t)(time_total_rampup * Thermostat[ctr_output].counter_rampup_cycles)) + Thermostat[ctr_output].temp_rampup_cycle; // Set auxiliary variables - Thermostat[ctr_output].time_rampup_nextcycle = uptime + (uint32_t)Thermostat[ctr_output].time_rampup_cycle; + Thermostat[ctr_output].time_rampup_nextcycle = uptime + ((uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60); Thermostat[ctr_output].temp_rampup_cycle = Thermostat[ctr_output].temp_measured; // Reset period counter Thermostat[ctr_output].counter_rampup_cycles = 1; @@ -785,7 +784,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output) // Increase the period counter Thermostat[ctr_output].counter_rampup_cycles++; // Set another period - Thermostat[ctr_output].time_rampup_nextcycle = uptime + (uint32_t)Thermostat[ctr_output].time_rampup_cycle; + Thermostat[ctr_output].time_rampup_nextcycle = uptime + ((uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60); // Reset time_ctr_changepoint and temp_rampup_output_off Thermostat[ctr_output].time_ctr_changepoint = uptime + (60 * (uint32_t)Thermostat[ctr_output].time_rampup_max) - time_in_rampup; Thermostat[ctr_output].temp_rampup_output_off = Thermostat[ctr_output].temp_target_level_ctr; @@ -1256,7 +1255,7 @@ void CmndTempMeasuredGrdRead(void) else { value = Thermostat[ctr_output].temp_measured_gradient; } - ResponseCmndFloat((float)value / 10, 1); + ResponseCmndFloat(((float)value) / 1000, 1); } } @@ -1280,25 +1279,11 @@ void CmndTimeManualToAutoSet(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_manual_to_auto = (uint16_t)(value / 60); + if ((value >= 0) && (value <= 1440)) { + Thermostat[ctr_output].time_manual_to_auto = (uint16_t)value; } } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_manual_to_auto * 60)); - } -} - -void CmndTimeOnLimitSet(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { - uint8_t ctr_output = XdrvMailbox.index - 1; - if (XdrvMailbox.data_len > 0) { - uint32_t value = (uint32_t)(XdrvMailbox.payload); - if ((value >= 0) && (value <= 86400)) { - Thermostat[ctr_output].time_on_limit = (uint16_t)(value / 60); - } - } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_on_limit * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_manual_to_auto)); } } @@ -1330,17 +1315,33 @@ void CmndTimeResetSet(void) } } +void CmndTimePiProportRead(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { + uint8_t ctr_output = XdrvMailbox.index - 1; + ResponseCmndNumber((int)Thermostat[ctr_output].time_proportional_pi); + } +} + +void CmndTimePiIntegrRead(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { + uint8_t ctr_output = XdrvMailbox.index - 1; + ResponseCmndNumber((int)Thermostat[ctr_output].time_integral_pi); + } +} + void CmndTimePiCycleSet(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { uint8_t ctr_output = XdrvMailbox.index - 1; if (XdrvMailbox.data_len > 0) { uint32_t value = (uint32_t)(XdrvMailbox.payload); - if ((value >= 0) && (value <= 86400)) { - Thermostat[ctr_output].time_pi_cycle = (uint16_t)(value / 60); + if ((value >= 0) && (value <= 1440)) { + Thermostat[ctr_output].time_pi_cycle = (uint16_t)(value); } } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_pi_cycle)); } } @@ -1404,11 +1405,11 @@ void CmndTimeMaxActionSet(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_max_action = (uint16_t)(value / 60); + if ((value >= 0) && (value <= 1440)) { + Thermostat[ctr_output].time_max_action = (uint16_t)value; } } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_max_action * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_max_action)); } } @@ -1418,11 +1419,11 @@ void CmndTimeMinActionSet(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_min_action = (uint16_t)(value / 60); + if ((value >= 0) && (value <= 1440)) { + Thermostat[ctr_output].time_min_action = (uint16_t)value; } } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_action * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_action)); } } @@ -1432,11 +1433,11 @@ void CmndTimeSensLostSet(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_sens_lost = (uint16_t)(value / 60); + if ((value >= 0) && (value <= 1440)) { + Thermostat[ctr_output].time_sens_lost = (uint16_t)value; } } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_sens_lost * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_sens_lost)); } } @@ -1446,11 +1447,11 @@ void CmndTimeMinTurnoffActionSet(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_min_turnoff_action = (uint16_t)(value / 60); + if ((value >= 0) && (value <= 1440)) { + Thermostat[ctr_output].time_min_turnoff_action = (uint16_t)value; } } - ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_turnoff_action * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_turnoff_action)); } } @@ -1514,11 +1515,11 @@ void CmndTimeRampupMaxSet(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_rampup_max = (uint16_t)(value / 60); + if ((value >= 0) && (value <= 1440)) { + Thermostat[ctr_output].time_rampup_max = (uint16_t)value; } } - ResponseCmndNumber((int)(((uint32_t)Thermostat[ctr_output].time_rampup_max) * 60)); + ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_rampup_max)); } } @@ -1528,7 +1529,7 @@ void CmndTimeRampupCycleSet(void) uint8_t ctr_output = XdrvMailbox.index - 1; if (XdrvMailbox.data_len > 0) { uint32_t value = (uint32_t)(XdrvMailbox.payload); - if ((value >= 0) && (value <= 54000)) { + if ((value >= 0) && (value <= 1440)) { Thermostat[ctr_output].time_rampup_cycle = (uint16_t)value; } } @@ -1563,22 +1564,6 @@ void CmndTempRampupPiAccErrSet(void) } } -void CmndTimePiProportRead(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { - uint8_t ctr_output = XdrvMailbox.index - 1; - ResponseCmndNumber((int)Thermostat[ctr_output].time_proportional_pi); - } -} - -void CmndTimePiIntegrRead(void) -{ - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { - uint8_t ctr_output = XdrvMailbox.index - 1; - ResponseCmndNumber((int)Thermostat[ctr_output].time_integral_pi); - } -} - void CmndDiagnosticModeSet(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { From b31455c8ebac97d3db7260d48786ec7609531e65 Mon Sep 17 00:00:00 2001 From: Javier Arigita Date: Sun, 10 May 2020 23:34:53 +0200 Subject: [PATCH 28/30] Minor correction --- tasmota/xdrv_39_thermostat.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_39_thermostat.ino b/tasmota/xdrv_39_thermostat.ino index 5c9fc2739..5301d9ca2 100644 --- a/tasmota/xdrv_39_thermostat.ino +++ b/tasmota/xdrv_39_thermostat.ino @@ -1338,7 +1338,7 @@ void CmndTimePiCycleSet(void) if (XdrvMailbox.data_len > 0) { uint32_t value = (uint32_t)(XdrvMailbox.payload); if ((value >= 0) && (value <= 1440)) { - Thermostat[ctr_output].time_pi_cycle = (uint16_t)(value); + Thermostat[ctr_output].time_pi_cycle = (uint16_t)value; } } ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_pi_cycle)); From 125ee62ff39ac922b16861b5057c4efc3188b925 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Mon, 11 May 2020 15:27:29 +0200 Subject: [PATCH 29/30] Add Serial buffer overrun detection Add Serial buffer overrun detection and fix serial corruption by increasing uart serial buffer to 520 --- tasmota/support_tasmota.ino | 21 ++++++++++++++++++--- tasmota/tasmota.h | 2 +- tasmota/tasmota.ino | 4 +++- tasmota/xdrv_99_debug.ino | 17 +++++++++++++++-- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 47aca2397..728d20f7f 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -1227,6 +1227,17 @@ void SerialInput(void) delay(0); serial_in_byte = Serial.read(); + if (0 == serial_in_byte_counter) { + serial_buffer_overrun = false; + } + else if ((serial_in_byte_counter == INPUT_BUFFER_SIZE) +#ifdef ESP8266 +// || Serial.hasOverrun() // Default ESP8266 Serial buffer size is 256. Tasmota increases to INPUT_BUFFER_SIZE +#endif + ) { + serial_buffer_overrun = true; + } + #ifdef ESP8266 /*-------------------------------------------------------------------------------------------*\ * Sonoff dual and ch4 19200 baud serial interface @@ -1255,7 +1266,7 @@ void SerialInput(void) if (serial_in_byte_counter < INPUT_BUFFER_SIZE -1) { // Add char to string if it still fits serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; } else { - serial_in_byte_counter = 0; + serial_buffer_overrun = true; // Signal overrun but continue reading input to flush until '\n' (EOL) } } } else { @@ -1292,8 +1303,12 @@ void SerialInput(void) if (!Settings.flag.mqtt_serial && (serial_in_byte == '\n')) { // CMND_SERIALSEND and CMND_SERIALLOG serial_in_buffer[serial_in_byte_counter] = 0; // Serial data completed seriallog_level = (Settings.seriallog_level < LOG_LEVEL_INFO) ? (uint8_t)LOG_LEVEL_INFO : Settings.seriallog_level; - AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_in_buffer); - ExecuteCommand(serial_in_buffer, SRC_SERIAL); + if (serial_buffer_overrun) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "Serial buffer overrun")); + } else { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_in_buffer); + ExecuteCommand(serial_in_buffer, SRC_SERIAL); + } serial_in_byte_counter = 0; serial_polling_window = 0; Serial.flush(); diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index ab152f9ec..3cb309910 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -124,7 +124,7 @@ const uint16_t SYSLOG_TIMER = 600; // Seconds to restore syslog_level const uint16_t SERIALLOG_TIMER = 600; // Seconds to disable SerialLog const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching the new firmware -const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in (serial and http) command buffer +const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in serial command buffer const uint16_t FLOATSZ = 16; // Max number of characters in float result from dtostrfd (max 32) const uint16_t CMDSZ = 24; // Max number of characters in command const uint16_t TOPSZ = 151; // Max number of characters in topic string diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 6425d7fae..d1d841810 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -156,7 +156,8 @@ uint8_t last_source = 0; // Last command source uint8_t shutters_present = 0; // Number of actual define shutters uint8_t prepped_loglevel = 0; // Delayed log level message //uint8_t mdns_delayed_start = 0; // mDNS delayed start -bool serial_local = false; // Handle serial locally; +bool serial_local = false; // Handle serial locally +bool serial_buffer_overrun = false; // Serial buffer overrun bool fallback_topic_flag = false; // Use Topic or FallbackTopic bool backlog_mutex = false; // Command backlog pending bool interlock_mutex = false; // Interlock power command pending @@ -215,6 +216,7 @@ void setup(void) RtcRebootSave(); Serial.begin(APP_BAUDRATE); + Serial.setRxBufferSize(INPUT_BUFFER_SIZE); // Default is 256 chars seriallog_level = LOG_LEVEL_INFO; // Allow specific serial messages until config loaded snprintf_P(my_version, sizeof(my_version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff); // Release version 6.3.0 diff --git a/tasmota/xdrv_99_debug.ino b/tasmota/xdrv_99_debug.ino index eeb4e7ca1..672ff17d3 100644 --- a/tasmota/xdrv_99_debug.ino +++ b/tasmota/xdrv_99_debug.ino @@ -59,13 +59,14 @@ #define D_CMND_I2CREAD "I2CRead" #define D_CMND_I2CSTRETCH "I2CStretch" #define D_CMND_I2CCLOCK "I2CClock" +#define D_CMND_SERBUFF "SerBufSize" const char kDebugCommands[] PROGMEM = "|" // No prefix D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|" #ifdef USE_WEBSERVER D_CMND_CFGXOR "|" #endif - D_CMND_CPUCHECK "|" + D_CMND_CPUCHECK "|" D_CMND_SERBUFF "|" #ifdef DEBUG_THEO D_CMND_EXCEPTION "|" #endif @@ -80,7 +81,7 @@ void (* const DebugCommand[])(void) PROGMEM = { #ifdef USE_WEBSERVER &CmndCfgXor, #endif - &CmndCpuCheck, + &CmndCpuCheck, &CmndSerBufSize, #ifdef DEBUG_THEO &CmndException, #endif @@ -479,6 +480,18 @@ void CmndCpuCheck(void) ResponseCmndNumber(CPU_load_check); } +void CmndSerBufSize(void) +{ + if (XdrvMailbox.data_len > 0) { + Serial.setRxBufferSize(XdrvMailbox.payload); + } +#ifdef ESP8266 + ResponseCmndNumber(Serial.getRxBufferSize()); +#else + ResponseCmndDone(); +#endif +} + void CmndFreemem(void) { if (XdrvMailbox.data_len > 0) { From 001b6e4895c59f98f10cf7e8acc4b11eadef4f46 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Mon, 11 May 2020 15:38:59 +0200 Subject: [PATCH 30/30] Fix Dew Point is null Fix Dew Point is null if either or both Temperature and/ot Humidity is null (#8411) --- tasmota/support.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index 5d7d06624..db4c9c26e 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -638,7 +638,7 @@ float ConvertHumidity(float h) float CalcTempHumToDew(float t, float h) { - if (isnan(h) || isnan(t)) { return 0.0; } + if (isnan(h) || isnan(t)) { return NAN; } if (Settings.flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit t = (t - 32) / 1.8; // Celsius