From 9ac3cce61daf3dae8fc606ebc93ebc5d9d953b26 Mon Sep 17 00:00:00 2001 From: Norbert Richter Date: Thu, 9 Jul 2020 12:31:37 +0200 Subject: [PATCH 01/13] Update python unishox tool from lib --- tools/unishox/unishox.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tools/unishox/unishox.py b/tools/unishox/unishox.py index 1cce14706..087932daa 100644 --- a/tools/unishox/unishox.py +++ b/tools/unishox/unishox.py @@ -344,15 +344,16 @@ class Unishox: #print("getCodeIdx not found = {r}".format(r=1)) return 1, bit_no_p - def getNumFromBits(self, inn, bit_no, count): + def getNumFromBits(self, inn, bit_no_p, count): ret = 0 while count: count -= 1 - if self.ESCAPE_MARKER == inn[bit_no >> 3]: - bit_no += 8 # skip marker - ret += self.getBitVal(inn, bit_no, count) - bit_no += 1 - return ret + if self.ESCAPE_MARKER == inn[bit_no_p >> 3]: + bit_no_p += 8 # skip marker + ret += self.getBitVal(inn, bit_no_p, count) + bit_no_p += 1 + # print("getNumFromBits = {r}".format(r=ret)) + return ret, bit_no_p def readCount(self, inn, bit_no_p, len_): (idx, bit_no_p) = self.getCodeIdx(self.us_hcode, inn, len_, bit_no_p) @@ -372,10 +373,10 @@ class Unishox: till += (1 << bit_len_idx) i += 1 - count = self.getNumFromBits(inn, bit_no_p, bit_len_idx) + base + (count, bit_no_p) = self.getNumFromBits(inn, bit_no_p, bit_len_idx) + count = count + base #print("readCount getNumFromBits = {count} ({bl})".format(count=count,bl=bit_len_idx)) - bit_no_p += bit_len_idx return count, bit_no_p def decodeRepeat(self, inn, len_, out, ol, bit_no): From 387800adc57d4fc1b4ec277b93362db17260f219 Mon Sep 17 00:00:00 2001 From: halfbakery <54818270+halfbakery@users.noreply.github.com> Date: Thu, 9 Jul 2020 19:39:11 +0200 Subject: [PATCH 02/13] Update RELEASENOTES.md More devices supported without additional scripting --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5790242d2..038f20687 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -94,3 +94,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add compile time interlock parameters (#8759) - Add compile time user template (#8766) - Add rotary encoder support for light dimmer and optional color temperature if button1 still pressed (#8670) +- Add support for switches/relays using an AC detection circuitry e.g. MOES MS-104B / BlitzWolf SS5 / etc. (#8606) From 481d26ce5f52a831408e674652cd0d571372837f Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Thu, 9 Jul 2020 20:58:17 +0200 Subject: [PATCH 03/13] Use core 2.7.2 --- platformio.ini | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index d03de291d..7a0a051b4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -110,8 +110,7 @@ build_flags = ${esp_defaults.build_flags} ; No exception code in firmware -fno-exceptions -lstdc++ - ; the following removes the 4-bytes alignment for PSTR(), waiting for a cleaner flag from Arduino Core - -DPSTR\(s\)=\(__extension__\(\{static\ const\ char\ __c\[\]\ __attribute__\(\(__aligned__\(1\)\)\)\ __attribute__\(\(section\(\ \"\\\\\".irom0.pstr.\"\ __FILE__\ \".\"\ __STRINGIZE\(__LINE__\)\ \".\"\ \ __STRINGIZE\(__COUNTER__\)\ \"\\\\\"\,\ \\\\\"aSM\\\\\"\,\ \@progbits\,\ 1\ \#\"\)\)\)\ =\ \(s\)\;\ \&__c\[0\]\;\}\)\) + ; the following removes the 4-bytes alignment for PSTR() -DPSTR_ALIGN=1 ; restrict to minimal mime-types -DMIMETYPE_MINIMAL @@ -123,8 +122,8 @@ build_flags = -DUSE_IR_REMOTE_FULL [core] -; *** Esp8266 Arduino core 2.7.1 -platform = espressif8266@2.5.3 +; *** Esp8266 Arduino core 2.7.2 +platform = espressif8266@2.6.0 platform_packages = build_unflags = ${esp_defaults.build_unflags} build_flags = ${esp82xx_defaults.build_flags} From 7f2ed318114a7f20c582a38f1fb7d978ffb414a8 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Thu, 9 Jul 2020 21:00:20 +0200 Subject: [PATCH 04/13] Use core 2.7.2 --- platformio_override_sample.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 9b43cd6a3..e8aedc5d7 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -87,7 +87,7 @@ extra_scripts = ${scripts_defaults.extra_scripts} [tasmota_stage] ; *** Esp8266 core for Arduino version Tasmota stage -platform = espressif8266@2.5.3 +platform = espressif8266@2.6.0 platform_packages = framework-arduinoespressif8266 @ https://github.com/tasmota/Arduino/releases/download/2.8.0-tasmota/esp8266-2.8.0.zip build_unflags = ${esp_defaults.build_unflags} build_flags = ${esp82xx_defaults.build_flags} @@ -123,7 +123,7 @@ build_flags = ${esp82xx_defaults.build_flags} [core_stage] ; *** Esp8266 core for Arduino version latest development version -platform = espressif8266@2.5.3 +platform = espressif8266@2.6.0 platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git build_unflags = ${esp_defaults.build_unflags} build_flags = ${esp82xx_defaults.build_flags} From 19aa95f7fb6958ee78443e58f65da0a3bb3b252d Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Thu, 9 Jul 2020 21:01:01 +0200 Subject: [PATCH 05/13] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 20d80249a..597c69eff 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ - [ ] The pull request is done against the latest dev branch - [ ] Only relevant files were touched - [ ] Only one feature/fix was added per PR. - - [ ] The code change is tested and works on core ESP8266 V.2.7.1 + - [ ] The code change is tested and works on core ESP8266 V.2.7.2 - [ ] The code change is tested and works on core ESP32 V.1.12.2 - [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla). From 2dd3e0e0a098e88d40206147b8f7b1d2d27a74f4 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 10 Jul 2020 20:15:12 +0200 Subject: [PATCH 06/13] Zigbee EZSP better support for groups --- tasmota/i18n.h | 1 + tasmota/xdrv_23_zigbee_0_constants.ino | 4 ++- tasmota/xdrv_23_zigbee_7_statemachine.ino | 3 ++ tasmota/xdrv_23_zigbee_8_parsers.ino | 6 ++++ tasmota/xdrv_23_zigbee_A_impl.ino | 34 +++++++++++++++++++++-- 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 194ff4bc6..eeedbfc57 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -509,6 +509,7 @@ #define D_CMND_ZIGBEEZNPRECEIVE "ZNPReceive" // only for debug #define D_CMND_ZIGBEE_EZSP_RECEIVE "EZSPReceive" // only for debug #define D_CMND_ZIGBEE_EZSP_RECEIVE_RAW "EZSPReceiveRaw" // only for debug +#define D_CMND_ZIGBEE_EZSP_LISTEN "Listen" // only for EZSP #define D_CMND_ZIGBEEZNPSEND "ZNPSend" #define D_CMND_ZIGBEE_EZSP_SEND "EZSPSend" #define D_CMND_ZIGBEE_EZSP_SEND_RAW "EZSPSendRaw" diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index da2dd5119..ea087e70f 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -427,7 +427,9 @@ enum EZSP_EmberOutgoingMessageType { EMBER_OUTGOING_VIA_ADDRESS_TABLE = 0x01, EMBER_OUTGOING_VIA_BINDING = 0x02, EMBER_OUTGOING_MULTICAST = 0x03, - EMBER_OUTGOING_BROADCAST = 0x04 + EMBER_OUTGOING_MULTICAST_WITH_ALIAS = 0x04, + EMBER_OUTGOING_BROADCAST_WITH_ALIAS = 0x05, + EMBER_OUTGOING_BROADCAST = 0x06 }; // inspired from https://github.com/zigpy/zigpy/blob/dev/zigpy/zdo/types.py diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index bc7fccf06..7b0c1adc6 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -717,6 +717,8 @@ ZBM(ZBS_SET_POLICY_02, EZSP_setPolicy, 0x00 /*high*/, EZSP_UNICAST_REPLIES_PO EZSP_HOST_WILL_NOT_SUPPLY_REPLY) // 55000220 ZBM(ZBS_SET_POLICY_03, EZSP_setPolicy, 0x00 /*high*/, EZSP_POLL_HANDLER_POLICY, EZSP_POLL_HANDLER_IGNORE) // 55000330 +ZBM(ZBS_SET_POLICY_04, EZSP_setPolicy, 0x00 /*high*/, EZSP_MESSAGE_CONTENTS_IN_CALLBACK_POLICY, + EZSP_MESSAGE_TAG_AND_CONTENTS_IN_CALLBACK) // 55000441 ZBM(ZBS_SET_POLICY_05, EZSP_setPolicy, 0x00 /*high*/, EZSP_TC_KEY_REQUEST_POLICY, EZSP_ALLOW_TC_KEY_REQUESTS_AND_SEND_CURRENT_KEY) // 55000551 ZBM(ZBS_SET_POLICY_06, EZSP_setPolicy, 0x00 /*high*/, EZSP_APP_KEY_REQUEST_POLICY, @@ -871,6 +873,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_SEND(ZBS_SET_POLICY_00) ZI_WAIT_RECV(500, ZBR_SET_POLICY_XX) ZI_SEND(ZBS_SET_POLICY_02) ZI_WAIT_RECV(500, ZBR_SET_POLICY_XX) ZI_SEND(ZBS_SET_POLICY_03) ZI_WAIT_RECV(500, ZBR_SET_POLICY_XX) + // ZI_SEND(ZBS_SET_POLICY_04) ZI_WAIT_RECV(500, ZBR_SET_POLICY_XX) ZI_SEND(ZBS_SET_POLICY_05) ZI_WAIT_RECV(500, ZBR_SET_POLICY_XX) ZI_SEND(ZBS_SET_POLICY_06) ZI_WAIT_RECV(500, ZBR_SET_POLICY_XX) diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 14e62b603..a94a07aeb 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -935,6 +935,12 @@ void Z_IncomingMessage(ZCLFrame &zcl_received) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str()); } + // discard the message if it was sent by us (broadcast or group loopback) + if (srcaddr == localShortAddr) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "loopback message, ignoring")); + return; // abort the rest of message management + } + zcl_received.postProcessAttributes(srcaddr, json); // Add Endpoint json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint; diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 7eeae7c15..e46d847eb 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -26,7 +26,7 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEEZNPRECEIVE "|" #endif // USE_ZIGBEE_ZNP #ifdef USE_ZIGBEE_EZSP - D_CMND_ZIGBEE_EZSP_SEND "|" D_CMND_ZIGBEE_EZSP_RECEIVE "|" + D_CMND_ZIGBEE_EZSP_SEND "|" D_CMND_ZIGBEE_EZSP_RECEIVE "|" D_CMND_ZIGBEE_EZSP_LISTEN "|" #endif // USE_ZIGBEE_EZSP D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" @@ -41,7 +41,7 @@ void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbZNPSend, &CmndZbZNPReceive, #endif // USE_ZIGBEE_ZNP #ifdef USE_ZIGBEE_EZSP - &CmndZbEZSPSend, &CmndZbEZSPReceive, + &CmndZbEZSPSend, &CmndZbEZSPReceive, &CmndZbEZSPListen, #endif // USE_ZIGBEE_EZSP &CmndZbPermitJoin, &CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe, @@ -1024,6 +1024,36 @@ void CmndZbPermitJoin(void) { ResponseCmndDone(); } +#ifdef USE_ZIGBEE_EZSP +// +// `ZbListen`: add a multicast group to listen to +// Overcomes a current limitation that EZSP only shows messages from multicast groups it listens too +// +// Ex: `ZbListen 99`, `ZbListen2 100` +void CmndZbEZSPListen(void) { + if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } + + int32_t index = XdrvMailbox.index - 1; // 0 based + int32_t group = XdrvMailbox.payload; + + if (group <= 0) { + group = 0; + } else if (group > 0xFFFF) { + group = 0xFFFF; + } + + SBuffer buf(8); + buf.add16(EZSP_setMulticastTableEntry); + buf.add8(index); + buf.add16(group); // group + buf.add8(0x01); // endpoint + buf.add8(0x00); // network index + ZigbeeEZSPSendCmd(buf.getBuffer(), buf.len(), true); + + ResponseCmndDone(); +} +#endif // USE_ZIGBEE_EZSP + // // Command `ZbStatus` // From 833f47fd1518c73b79d9d720bfc87d970cc2011f Mon Sep 17 00:00:00 2001 From: Khoa Ton Date: Sat, 11 Jul 2020 23:16:29 -0700 Subject: [PATCH 07/13] Support up to 8 I2C Seven Segment Displays --- tasmota/my_user_config.h | 2 +- tasmota/xdsp_11_sevenseg.ino | 95 ++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 32 deletions(-) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 0ab09d224..3447295a0 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -559,7 +559,7 @@ #define MTX_ADDRESS7 0x00 // [DisplayAddress7] I2C address of seventh 8x8 matrix module #define MTX_ADDRESS8 0x00 // [DisplayAddress8] I2C address of eigth 8x8 matrix module #define USE_DISPLAY_SEVENSEG // [DisplayModel 11] [I2cDriver47] Enable sevenseg display (I2C 0x70-0x77) (<+11k code) - #define SEVENSEG_ADDRESS1 0x70 // [DisplayAddress1] I2C address of first sevenseg matrix module + // #define SEVENSEG_ADDRESS1 0x70 // No longer used. Use MTX_ADDRESS1 - MTX_ADDRESS8 instead to specify I2C address of sevenseg displays // #define USE_DISPLAY_SH1106 // [DisplayModel 7] [I2cDriver6] Enable SH1106 Oled 128x64 display (I2C addresses 0x3C and 0x3D) #endif // USE_I2C diff --git a/tasmota/xdsp_11_sevenseg.ino b/tasmota/xdsp_11_sevenseg.ino index 20193e54e..0978cc466 100644 --- a/tasmota/xdsp_11_sevenseg.ino +++ b/tasmota/xdsp_11_sevenseg.ino @@ -28,20 +28,24 @@ #include #include // Seven segment LED -Adafruit_7segment sevenseg = Adafruit_7segment(); - +Adafruit_7segment *sevenseg[8]; +uint8_t sevensegs = 0; uint8_t sevenseg_state = 0; /*********************************************************************************************/ void SevensegWrite(void) { - sevenseg.writeDisplay(); + for (uint32_t i = 0; i < sevensegs; i++) { + sevenseg[i]->writeDisplay(); + } } void SevensegClear(void) { - sevenseg.clear(); + for (uint32_t i = 0; i < sevensegs; i++) { + sevenseg[i]->clear(); + } SevensegWrite(); } @@ -50,8 +54,10 @@ void SevensegClear(void) void SevensegInitMode(void) { - sevenseg.setBrightness(Settings.display_dimmer); - sevenseg.blinkRate(0); // 0 - 3 + for (uint32_t i = 0; i < sevensegs; i++) { + sevenseg[i]->setBrightness(Settings.display_dimmer); + sevenseg[i]->blinkRate(0); + } SevensegClear(); } @@ -69,17 +75,25 @@ void SevensegInit(uint8_t mode) void SevensegInitDriver(void) { if (!Settings.display_model) { - if (I2cSetDevice(SEVENSEG_ADDRESS1)) { + if (I2cSetDevice(Settings.display_address[0])) { Settings.display_model = XDSP_11; } } if (XDSP_11 == Settings.display_model) { sevenseg_state = 1; - sevenseg.begin(SEVENSEG_ADDRESS1); - + for (sevensegs = 0; sevensegs < 8; sevensegs++) { + if (Settings.display_address[sevensegs]) { + I2cSetActiveFound(Settings.display_address[sevensegs], "SevenSeg"); + sevenseg[sevensegs] = new Adafruit_7segment(); + sevenseg[sevensegs]->begin(Settings.display_address[sevensegs]); + } else { + break; + } + } + Settings.display_width = 4; - Settings.display_height = 1; + Settings.display_height = sevensegs; SevensegInitMode(); } @@ -101,15 +115,34 @@ void SevensegDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uin boolean hex=false; boolean done=false; boolean s=false; + uint8_t unit=y; + if ((unit>=sevensegs) || (unit<0)) { + unit=0; + } + for (int i=0; (str[i]!='\0') && (!done); i++) { - // [prefix(es) chars]digits + // [optional prefix(es) chars]digits // Some combinations won't make sense. // Reference: https://learn.adafruit.com/adafruit-led-backpack/1-2-inch-7-segment-backpack-arduino-wiring-and-setup + // + // Prefixes: + // x upcoming number decimal integer displayed as hex + // : turn on middle colon + // ^ turh on top left dot + // v turn on bottom left dot + // . turn on AM/PM/Degree dot + // s upcoming number is seconds, print as HH:MM or MM:SS + // z clear this display + // // Some sample valid combinations: - // 787 -> 787 - // x47 -> 2F - // st:241 -> 04:01 - // sT241 -> 4 01 + // 787 -> 787 + // x47 -> 2F + // s:241 -> 04:01 + // s241 -> 4 01 + // s1241 -> 20:41 + // z -> + // x88 -> 58 + switch (str[i]) { case 'x': // print given dec value as hex hex = true; @@ -126,12 +159,6 @@ void SevensegDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uin case '.': // print ampm dots |= 0x10; break; - case 'T': // print as time 12 format - t = true; - break; - case 't': // print as time 24 format - T = true; - break; case 's': // duration in seconds s = true; break; @@ -149,6 +176,12 @@ void SevensegDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uin number = atoi(str+i); done = true; break; + case 'z': // Clear this display + hasnumber=false; + dots=0; + s=false; + sevenseg[unit]->clear(); + break; default: // unknown format, ignore break; } @@ -171,17 +204,17 @@ void SevensegDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uin if (hasnumber) { if (hex) { - sevenseg.print(number, HEX); + sevenseg[unit]->print(number, HEX); } else { - sevenseg.print(number, DEC); + sevenseg[unit]->print(number, DEC); } } if (dots) { - sevenseg.writeDigitRaw(2, dots); + sevenseg[unit]->writeDigitRaw(2, dots); } - sevenseg.writeDisplay(); + sevenseg[unit]->writeDisplay(); } /*********************************************************************************************/ @@ -210,7 +243,7 @@ void SevensegTime(boolean time_24) // Now print the time value to the display. - sevenseg.print(displayValue, DEC); + sevenseg[0]->print(displayValue, DEC); // Add zero padding when in 24 hour mode and it's midnight. // In this case the print function above won't have leading 0's @@ -218,15 +251,15 @@ void SevensegTime(boolean time_24) if (time_24) { if (hours == 0) { // Pad hour 0. - sevenseg.writeDigitNum(1, 0); + sevenseg[0]->writeDigitNum(1, 0); // Also pad when the 10's minute is 0 and should be padded. if (minutes < 10) { - sevenseg.writeDigitNum(3, 0); + sevenseg[0]->writeDigitNum(3, 0); } } if (hours < 10) { // Always have 4 digits time - sevenseg.writeDigitNum(0, 0); + sevenseg[0]->writeDigitNum(0, 0); } } else { // Identify and display AM/PM @@ -235,8 +268,8 @@ void SevensegTime(boolean time_24) } } - sevenseg.writeDigitRaw(2, dots |= ((second%2) << 1)); - sevenseg.writeDisplay(); + sevenseg[0]->writeDigitRaw(2, dots |= ((second%2) << 1)); + sevenseg[0]->writeDisplay(); } void SevensegRefresh(void) // Every second From 7de43e6233f17347ed74b966719bd8b97cca2ef1 Mon Sep 17 00:00:00 2001 From: Khoa Ton Date: Sat, 11 Jul 2020 23:33:25 -0700 Subject: [PATCH 08/13] Added comments on multiple sevenseg display layout --- tasmota/my_user_config.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 3447295a0..3d9e904ca 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -559,6 +559,10 @@ #define MTX_ADDRESS7 0x00 // [DisplayAddress7] I2C address of seventh 8x8 matrix module #define MTX_ADDRESS8 0x00 // [DisplayAddress8] I2C address of eigth 8x8 matrix module #define USE_DISPLAY_SEVENSEG // [DisplayModel 11] [I2cDriver47] Enable sevenseg display (I2C 0x70-0x77) (<+11k code) + // Multiple sevenseg displays are logically arranged vertically with MTX_ADDRESS1 at y=0, + // MTX_ADDRESS2 at y=1, up to MTX_ADDRESS8 at y=7 + // Command: DisplayText [yn]8888 + // will display 8888 at sevenseg display at I2C address MTX_ADDRESS(n-1) // #define SEVENSEG_ADDRESS1 0x70 // No longer used. Use MTX_ADDRESS1 - MTX_ADDRESS8 instead to specify I2C address of sevenseg displays // #define USE_DISPLAY_SH1106 // [DisplayModel 7] [I2cDriver6] Enable SH1106 Oled 128x64 display (I2C addresses 0x3C and 0x3D) #endif // USE_I2C From 3462c7f31b76ffb341b16dbb2d10171d3cdc1d44 Mon Sep 17 00:00:00 2001 From: Khoa Ton Date: Sun, 12 Jul 2020 00:02:39 -0700 Subject: [PATCH 09/13] Fix mispelling in comment --- tasmota/xdsp_11_sevenseg.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdsp_11_sevenseg.ino b/tasmota/xdsp_11_sevenseg.ino index 0978cc466..d01637204 100644 --- a/tasmota/xdsp_11_sevenseg.ino +++ b/tasmota/xdsp_11_sevenseg.ino @@ -128,7 +128,7 @@ void SevensegDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uin // Prefixes: // x upcoming number decimal integer displayed as hex // : turn on middle colon - // ^ turh on top left dot + // ^ turn on top left dot // v turn on bottom left dot // . turn on AM/PM/Degree dot // s upcoming number is seconds, print as HH:MM or MM:SS From e3fd1b7850974ef9d28e10420410be0249fd1eee Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 12 Jul 2020 15:24:42 +0200 Subject: [PATCH 10/13] Fix DS18S20 temp calculation Fix DS18S20 temp calculation (#8777) --- tasmota/xsns_05_ds18x20.ino | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tasmota/xsns_05_ds18x20.ino b/tasmota/xsns_05_ds18x20.ino index a762014c7..361847932 100644 --- a/tasmota/xsns_05_ds18x20.ino +++ b/tasmota/xsns_05_ds18x20.ino @@ -375,12 +375,19 @@ bool Ds18x20Read(uint8_t sensor) if (OneWireCrc8(data)) { switch(ds18x20_sensor[index].address[0]) { case DS18S20_CHIPID: { +/* if (data[1] > 0x80) { data[0] = (~data[0]) +1; sign = -1; // App-Note fix possible sign error } float temp9 = (float)(data[0] >> 1) * sign; ds18x20_sensor[index].temperature = ConvertTemp((temp9 - 0.25) + ((16.0 - data[6]) / 16.0)); + + Replaced by below based on issue #8777 +*/ + int16_t tempS = (((data[1] << 8) | (data[0] & 0xFE)) << 3) | ((0x10 - data[6]) & 0x0F); + ds18x20_sensor[index].temperature = ConvertTemp(tempS * 0.0625 - 0.250); + ds18x20_sensor[index].valid = SENSOR_MAX_MISS; return true; } From 202326c5f3fd6cc6158c94f54da93d708aedfc9d Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 12 Jul 2020 15:34:02 +0200 Subject: [PATCH 11/13] Update changelog --- RELEASENOTES.md | 8 ++++---- tasmota/CHANGELOG.md | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 038f20687..fdafb0535 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -21,7 +21,7 @@ While fallback or downgrading is common practice it was never supported due to S ## Supported Core versions -This release will be supported from ESP8266/Arduino library Core version **2.7.1** due to reported security and stability issues on previous Core version. This will also support gzipped binaries. +This release will be supported from ESP8266/Arduino library Core version **2.7.2** due to reported security and stability issues on previous Core version. This will also support gzipped binaries. Although it might still compile on previous Core versions all support will be removed in the near future. @@ -35,7 +35,7 @@ For initial configuration this release supports Webserver based **WifiManager** ## Provided Binary Downloads -The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.1**. +The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.2**. - **tasmota.bin** = The Tasmota version with most drivers. **RECOMMENDED RELEASE BINARY** - **tasmota-BG.bin** to **tasmota-TW.bin** = The Tasmota version in different languages. @@ -52,7 +52,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c ## Changelog -### Version 8.4.0 George +### Version 8.3.1.6 - Change IRremoteESP8266 library updated to v2.7.7 - Change Adafruit_SGP30 library from v1.0.3 to v1.2.0 (#8519) @@ -94,4 +94,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add compile time interlock parameters (#8759) - Add compile time user template (#8766) - Add rotary encoder support for light dimmer and optional color temperature if button1 still pressed (#8670) -- Add support for switches/relays using an AC detection circuitry e.g. MOES MS-104B / BlitzWolf SS5 / etc. (#8606) +- Add support for switches/relays using an AC detection circuitry e.g. MOES MS-104B or BlitzWolf SS5 (#8606) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 0d98612e1..a459c2aac 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -1,9 +1,3 @@ -## Released - -### 8.4.0 20200716 - -- Release George - ## Unreleased (development) ### 8.3.1.6 20200617 @@ -17,6 +11,7 @@ - Add compile time interlock parameters (#8759) - Add compile time user template (#8766) - Add rotary encoder support for light dimmer and optional color temperature if button1 still pressed (#8670) +- Add support for switches/relays using an AC detection circuitry e.g. MOES MS-104B or BlitzWolf SS5 (#8606) - Fix exception or watchdog on rule re-entry (#8757) - Change ESP32 USER GPIO template representation decreasing template message size - Change define USE_TASMOTA_SLAVE into USE_TASMOTA_CLIENT @@ -67,6 +62,8 @@ - Add support for VEML6075 UVA/UVB/UVINDEX Sensor by device111 (#8432) - Add support for VEML7700 Ambient light intensity Sensor by device111 (#8432) +## Released + ### 8.3.1 20200518 - Release Fred @@ -83,6 +80,8 @@ - Change Quick Power Cycle detection from 4 to 7 power interrupts (#4066) - Fix default state of ``SetOption73 0`` for button decoupling and send multi-press and hold MQTT messages +## Released + ### 8.3.0 20200514 - Release Fred @@ -167,6 +166,8 @@ - Add support for unreachable (unplugged) Zigbee devices in Philips Hue emulation and Alexa - Add support for 64x48 SSD1306 OLED (#6740) +## Released + ### 8.2.0 20200321 - Release Elliot From 39705a2e059cf616199c1e18eb04a87d00482b7c Mon Sep 17 00:00:00 2001 From: Staars Date: Sun, 12 Jul 2020 18:17:27 +0200 Subject: [PATCH 12/13] add lights, yeerc, decryption and more --- tasmota/xsns_62_MI_ESP32.ino | 809 +++++++++++++++++++++++------------ 1 file changed, 535 insertions(+), 274 deletions(-) diff --git a/tasmota/xsns_62_MI_ESP32.ino b/tasmota/xsns_62_MI_ESP32.ino index 9fa6a71d8..6ec2d6f83 100644 --- a/tasmota/xsns_62_MI_ESP32.ino +++ b/tasmota/xsns_62_MI_ESP32.ino @@ -20,7 +20,11 @@ -------------------------------------------------------------------------------------------- Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- + 0.9.1.0 20200712 changed - add lights and yeerc, add pure passive mode with decryption, + lots of refactoring + ------- 0.9.0.1 20200706 changed - adapt to new NimBLE-API, tweak scan process + ------- 0.9.0.0 20200413 started - initial development by Christian Baars forked - from arendst/tasmota - https://github.com/arendst/Tasmota @@ -30,9 +34,13 @@ #ifdef USE_MI_ESP32 #define XSNS_62 62 +#define USE_MI_DECRYPTION #include #include +#ifdef USE_MI_DECRYPTION +#include +#endif //USE_MI_DECRYPTION void MI32scanEndedCB(NimBLEScanResults results); void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); @@ -56,6 +64,10 @@ struct { uint32_t willReadBatt:1; uint32_t shallSetUnit:1; uint32_t willSetUnit:1; + uint32_t triggeredTele:1; + uint32_t shallClearResults:1; + uint32_t directMQTT:1; // TODO: direct bridging of every single sensor message + } mode; struct { uint8_t sensor; // points to to the number 0...255 @@ -87,7 +99,7 @@ struct mi_beacon_t{ uint16_t frame; uint16_t productID; uint8_t counter; - uint8_t Mac[6]; + uint8_t MAC[6]; uint8_t spare; uint8_t type; uint8_t ten; @@ -103,12 +115,18 @@ struct mi_beacon_t{ uint32_t lux; //07 uint8_t moist; //08 uint16_t fert; //09 + uint32_t NMT; //17 + struct{ //01 + uint16_t num; + uint8_t longPress; + }Btn; }; + uint8_t padding[12]; }; struct cg_packet_t { uint16_t frameID; - uint8_t serial[6]; + uint8_t MAC[6]; uint16_t mode; union { struct { @@ -119,34 +137,58 @@ struct cg_packet_t { }; }; +struct encPacket_t{ + // the packet is longer, but this part is enough to decrypt + uint16_t PID; + uint8_t frameCnt; + uint8_t MAC[6]; + uint8_t payload[16]; // only a pointer to the address, size is variable +}; + +union mi_bindKey_t{ + struct{ + uint8_t key[16]; + uint8_t MAC[6]; + }; + uint8_t buf[22]; +}; + #pragma pack(0) struct mi_sensor_t{ uint8_t type; //Flora = 1; MI-HT_V1=2; LYWSD02=3; LYWSD03=4; CGG1=5; CGD1=6 - uint8_t serial[6]; - uint8_t showedUp; + uint8_t lastCnt; //device generated counter of the packet + uint8_t shallSendMQTT; + uint8_t MAC[6]; + // uint8_t showedUp; + uint32_t lastTime; + uint32_t lux; float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx union { struct { float moisture; float fertility; - uint32_t lux; + char firmware[6]; // actually only for FLORA but hopefully we can add for more devices }; // Flora struct { float hum; }; // MJ_HT_V1, LYWSD0x + struct { + uint16_t events; //"alarms" since boot + uint32_t NMT; // no motion time in seconds for the MJYD2S + uint8_t eventType; //internal type of actual event for the MJYD2S -> 1: PIR, 2: No PIR, 3: NMT + }; + uint16_t Btn; }; - union - { - uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) - uint16_t volt; // LYWSD03MMC + union { + uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) }; - char firmware[6]; // actually only for FLORA but hopefully we can add for more devices }; std::vector MIBLEsensors; -BLEScan* MI32Scan; -BLEScanResults MI32foundDevices; +std::vector MIBLEbindKeys; + +static BLEScan* MI32Scan; /*********************************************************************************************\ * constants @@ -156,7 +198,7 @@ BLEScanResults MI32foundDevices; const char S_JSON_MI32_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_MI32 "%s\":%d}"; const char S_JSON_MI32_COMMAND[] PROGMEM = "{\"" D_CMND_MI32 "%s%s\"}"; -const char kMI32_Commands[] PROGMEM = "Period|Time|Page|Battery|Unit"; +const char kMI32_Commands[] PROGMEM = "Period|Time|Page|Battery|Unit|Key"; #define FLORA 1 #define MJ_HT_V1 2 @@ -164,22 +206,31 @@ const char kMI32_Commands[] PROGMEM = "Period|Time|Page|Battery|Unit #define LYWSD03MMC 4 #define CGG1 5 #define CGD1 6 +#define NLIGHT 7 +#define MJYD2S 8 +#define YEERC 9 -const uint16_t kMI32SlaveID[6]={ 0x0098, // Flora +const uint16_t kMI32DeviceID[9]={ 0x0098, // Flora 0x01aa, // MJ_HT_V1 0x045b, // LYWSD02 0x055b, // LYWSD03 0x0347, // CGG1 - 0x0576 // CGD1 + 0x0576, // CGD1 + 0x03dd, // NLIGHT + 0x07f6, // MJYD2S + 0x0153 // yee-rc }; -const char kMI32SlaveType1[] PROGMEM = "Flora"; -const char kMI32SlaveType2[] PROGMEM = "MJ_HT_V1"; -const char kMI32SlaveType3[] PROGMEM = "LYWSD02"; -const char kMI32SlaveType4[] PROGMEM = "LYWSD03"; -const char kMI32SlaveType5[] PROGMEM = "CGG1"; -const char kMI32SlaveType6[] PROGMEM = "CGD1"; -const char * kMI32SlaveType[] PROGMEM = {kMI32SlaveType1,kMI32SlaveType2,kMI32SlaveType3,kMI32SlaveType4,kMI32SlaveType5,kMI32SlaveType6}; +const char kMI32DeviceType1[] PROGMEM = "Flora"; +const char kMI32DeviceType2[] PROGMEM = "MJ_HT_V1"; +const char kMI32DeviceType3[] PROGMEM = "LYWSD02"; +const char kMI32DeviceType4[] PROGMEM = "LYWSD03"; +const char kMI32DeviceType5[] PROGMEM = "CGG1"; +const char kMI32DeviceType6[] PROGMEM = "CGD1"; +const char kMI32DeviceType7[] PROGMEM = "NLIGHT"; +const char kMI32DeviceType8[] PROGMEM = "MJYD2S"; +const char kMI32DeviceType9[] PROGMEM = "YEERC"; +const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4,kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8,kMI32DeviceType9}; /*********************************************************************************************\ * enumerations @@ -190,7 +241,8 @@ enum MI32_Commands { // commands useable in console or rules CMND_MI32_TIME, // set LYWSD02-Time from ESP8266-time CMND_MI32_PAGE, // sensor entries per web page, which will be shown alternated CMND_MI32_BATTERY, // read all battery levels - CMND_MI32_UNIT // toggles the displayed unit between C/F (LYWSD02) + CMND_MI32_UNIT, // toggles the displayed unit between C/F (LYWSD02) + CMND_MI32_KEY // add bind key to a mac for packet decryption }; enum MI32_TASK { @@ -207,13 +259,13 @@ enum MI32_TASK { class MI32SensorCallback : public NimBLEClientCallbacks { void onConnect(NimBLEClient* pclient) { - AddLog_P2(LOG_LEVEL_DEBUG,PSTR("connected %s"), kMI32SlaveType[(MIBLEsensors[MI32.state.sensor].type)-1]); + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("connected %s"), kMI32DeviceType[(MIBLEsensors[MI32.state.sensor].type)-1]); MI32.mode.willConnect = 0; MI32.mode.connected = 1; } void onDisconnect(NimBLEClient* pclient) { MI32.mode.connected = 0; - AddLog_P2(LOG_LEVEL_DEBUG,PSTR("disconnected %s"), kMI32SlaveType[(MIBLEsensors[MI32.state.sensor].type)-1]); + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("disconnected %s"), kMI32DeviceType[(MIBLEsensors[MI32.state.sensor].type)-1]); } bool onConnParamsUpdateRequest(NimBLEClient* MI32Client, const ble_gap_upd_params* params) { if(params->itvl_min < 24) { /** 1.25ms units */ @@ -234,6 +286,7 @@ class MI32AdvCallbacks: public NimBLEAdvertisedDeviceCallbacks { // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Advertised Device: %s Buffer: %u"),advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData().length()); if (advertisedDevice->getServiceData().length() == 0) { // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("No Xiaomi Device: %s Buffer: %u"),advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData().length()); + MI32Scan->erase(advertisedDevice->getAddress()); return; } uint16_t uuid = advertisedDevice->getServiceDataUUID().getNative()->u16.value; @@ -243,13 +296,12 @@ class MI32AdvCallbacks: public NimBLEAdvertisedDeviceCallbacks { MI32_ReverseMAC(addr); if(uuid==0xfe95) { MI32ParseResponse((char*)advertisedDevice->getServiceData().data(),advertisedDevice->getServiceData().length(), addr); - MI32Scan->erase(advertisedDevice->getAddress()); } else if(uuid==0xfdcd) { MI32parseCGD1Packet((char*)advertisedDevice->getServiceData().data(),advertisedDevice->getServiceData().length(), addr); - MI32Scan->erase(advertisedDevice->getAddress()); } else { + MI32Scan->erase(advertisedDevice->getAddress()); // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("No Xiaomi Device: %s Buffer: %u"),advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData().length()); } }; @@ -293,6 +345,125 @@ void MI32_ReverseMAC(uint8_t _mac[]){ memcpy(_mac,_reversedMAC, sizeof(_reversedMAC)); } +#ifdef USE_MI_DECRYPTION +void MI32AddKey(char* payload){ + mi_bindKey_t keyMAC; + memset(keyMAC.buf,0,sizeof(keyMAC)); + MI32KeyMACStringToBytes(payload,keyMAC.buf); + bool unknownKey = true; + for(uint32_t i=0; i= '0' && c <= '9') + value = (c - '0'); + else if (c >= 'A' && c <= 'F') + value = (10 + (c - 'A')); + _keyMAC[(index/2)] += value << (((index + 1) % 2) * 4); + index++; + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MI32: %s to:"),_string); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MI32: key-array: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X"),_keyMAC[0],_keyMAC[1],_keyMAC[2],_keyMAC[3],_keyMAC[4],_keyMAC[5],_keyMAC[6],_keyMAC[7],_keyMAC[8],_keyMAC[9],_keyMAC[10],_keyMAC[11],_string,_keyMAC[12],_keyMAC[13],_keyMAC[14],_keyMAC[15]); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MI32: MAC-array: %02X%02X%02X%02X%02X%02X"),_keyMAC[16],_keyMAC[17],_keyMAC[18],_keyMAC[19],_keyMAC[20],_keyMAC[21]); +} + +/** + * @brief Decrypts payload in place + * + * @param _buf - pointer to the buffer at position of PID + * @param _bufSize - buffersize (last position is last byte of TAG) + * @param _type - sensor type + * @return int - error code, 0 for success + */ +int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){ + encPacket_t *packet = (encPacket_t*)_buf; + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("to decrypt: %02x %02x %02x %02x %02x %02x %02x %02x"),(uint8_t)_buf[0],(uint8_t)_buf[1],(uint8_t)_buf[2],(uint8_t)_buf[3],(uint8_t)_buf[4],(uint8_t)_buf[5],(uint8_t)_buf[6],(uint8_t)_buf[7]); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR(" : %02x %02x %02x %02x %02x %02x %02x %02x"),(uint8_t)_buf[8],(uint8_t)_buf[9],(uint8_t)_buf[10],(uint8_t)_buf[11],(uint8_t)_buf[12],(uint8_t)_buf[13],(uint8_t)_buf[14],(uint8_t)_buf[15]); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR(" : %02x %02x %02x %02x %02x %02x %02x %02x"),(uint8_t)_buf[16],(uint8_t)_buf[17],(uint8_t)_buf[18],(uint8_t)_buf[19],(uint8_t)_buf[20],(uint8_t)_buf[21],(uint8_t)_buf[22],(uint8_t)_buf[23]); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("as packet: MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); + + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Decrypt Size of Buffer: %u"), _bufSize); + int ret = 0; + unsigned char output[10] = {0}; + uint8_t nonce[12]; + uint8_t tag[4 ]; + const unsigned char authData[1] = {0x11}; + + // nonce: device MAC, device type, frame cnt, ext. cnt + for (uint32_t i = 0; i<6; i++){ + nonce[i] = packet->MAC[i]; + } + memcpy((uint8_t*)&nonce+6,(uint8_t*)&packet->PID,2); + nonce[8] = packet->frameCnt; + memcpy((uint8_t*)&nonce+9,(char*)&_buf[_bufSize-9],3); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("nonceCnt1 and 2: %02x %02x %02x"),nonce[9],nonce[10],nonce[11]); + memcpy((uint8_t*)&tag,(char*)&_buf[_bufSize-6],4); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("tag: %02x %02x %02x %02x"),tag[0],tag[1],tag[2],tag[3]); + + MI32_ReverseMAC(packet->MAC); + uint8_t _bindkey[16] = {0x0}; + for(uint32_t i=0; iMAC,MIBLEbindKeys[i].MAC,sizeof(packet->MAC))==0){ + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("have key")); + memcpy(_bindkey,MIBLEbindKeys[i].key,sizeof(_bindkey)); + } + else{ + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mac in packet: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mac in vector: %02x %02x %02x %02x %02x %02x"), MIBLEbindKeys[i].MAC[0], MIBLEbindKeys[i].MAC[1], MIBLEbindKeys[i].MAC[2], MIBLEbindKeys[i].MAC[3], MIBLEbindKeys[i].MAC[4], MIBLEbindKeys[i].MAC[5]); + } + } + + // init + mbedtls_ccm_context ctx; + mbedtls_ccm_init(&ctx); + // set bind key + ret = mbedtls_ccm_setkey(&ctx, + MBEDTLS_CIPHER_ID_AES, + _bindkey, + 16 * 8 //bits + ); + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("set key: %i, MAC: %02x %02x %02x %02x %02x %02x"),ret, packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); + +/*int mbedtls_ccm_auth_decrypt( mbedtls_ccm_context *ctx, size_t length, + const unsigned char *iv, size_t iv_len, + const unsigned char *add, size_t add_len, + const unsigned char *input, unsigned char *output, + const unsigned char *tag, size_t tag_len ) +*/ + ret = mbedtls_ccm_auth_decrypt(&ctx,_bufSize-18, + (const unsigned char*)&nonce, sizeof(nonce), + authData, sizeof(authData), + (const unsigned char*)packet->payload, output, + tag,sizeof(tag)); + + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Decrypted %i: %02x %02x %02x %02x %02x %02x %02x"), ret, output[0],output[1],output[2],output[3],output[4],output[5],output[6]); + // put decrypted data in place + memcpy((uint8_t*)(packet->payload)+1,output,_bufSize-18); + // clean up + mbedtls_ccm_free(&ctx); + return ret; +} +#endif // USE_MI_DECRYPTION + /*********************************************************************************************\ * common functions \*********************************************************************************************/ @@ -301,63 +472,82 @@ void MI32_ReverseMAC(uint8_t _mac[]){ /** * @brief Return the slot number of a known sensor or return create new sensor slot * - * @param _serial BLE address of the sensor + * @param _MAC BLE address of the sensor * @param _type Type number of the sensor * @return uint32_t Known or new slot in the sensors-vector */ -uint32_t MIBLEgetSensorSlot(uint8_t (&_serial)[6], uint16_t _type){ +uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter){ DEBUG_SENSOR_LOG(PSTR("%s: will test ID-type: %x"),D_CMND_MI32, _type); bool _success = false; - for (uint32_t i=0;i<6;i++){ // i < sizeof(kMI32SlaveID) gives compiler warning - if(_type == kMI32SlaveID[i]){ + for (uint32_t i=0;i<9;i++){ // i < sizeof(kMI32DeviceID) gives compiler warning + if(_type == kMI32DeviceID[i]){ DEBUG_SENSOR_LOG(PSTR("MI32: ID is type %u"), i); _type = i+1; _success = true; } else { - DEBUG_SENSOR_LOG(PSTR("%s: ID-type is not: %x"),D_CMND_MI32,kMI32SlaveID[i]); + DEBUG_SENSOR_LOG(PSTR("%s: ID-type is not: %x"),D_CMND_MI32,kMI32DeviceID[i]); } } if(!_success) return 0xff; DEBUG_SENSOR_LOG(PSTR("%s: vector size %u"),D_CMND_MI32, MIBLEsensors.size()); for(uint32_t i=0; isetClientCallbacks(&MI32SensorCB , false); MI32Client->setConnectionParams(12,12,0,48); MI32Client->setConnectTimeout(30); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: did create new client"),D_CMND_MI32); } + vTaskDelay(300/ portTICK_PERIOD_MS); if (!MI32Client->connect(_address,false)) { MI32.mode.willConnect = 0; - NimBLEDevice::deleteClient(MI32Client); - DEBUG_SENSOR_LOG(PSTR("%s: did not connect client"),D_CMND_MI32); + // NimBLEDevice::deleteClient(MI32Client); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: did not connect client"),D_CMND_MI32); return false; } - DEBUG_SENSOR_LOG(PSTR("%s: did create new client"),D_CMND_MI32); return true; // } } - void MI32StartScanTask(){ if (MI32.mode.connected) return; MI32.mode.runningScan = 1; - // Wifi.counter = Wifi.counter + 3; xTaskCreatePinnedToCore( MI32ScanTask, /* Function to implement the task */ "MI32ScanTask", /* Name of the task */ - 8192, /* Stack size in words */ + 4096, /* Stack size in words */ NULL, /* Task input parameter */ 0, /* Priority of the task */ NULL, /* Task handle. */ @@ -470,26 +658,33 @@ void MI32StartScanTask(){ void MI32ScanTask(void *pvParameters){ if (MI32Scan == nullptr) MI32Scan = NimBLEDevice::getScan(); DEBUG_SENSOR_LOG(PSTR("%s: Scan Cache Length: %u"),D_CMND_MI32, MI32Scan->getResults().getCount()); - MI32Scan->setAdvertisedDeviceCallbacks(&MI32ScanCallbacks); + MI32Scan->setInterval(70); + MI32Scan->setWindow(50); + MI32Scan->setAdvertisedDeviceCallbacks(&MI32ScanCallbacks,true); MI32Scan->setActiveScan(false); - MI32Scan->start(5, MI32scanEndedCB, true); // hard coded duration + MI32Scan->start(0, MI32scanEndedCB, true); // never stop scanning, will pause automaically while connecting + uint32_t timer = 0; - while (MI32.mode.runningScan){ - if (timer>15){ - vTaskDelete( NULL ); + for(;;){ + if(MI32.mode.shallClearResults){ + MI32Scan->clearResults(); + MI32.mode.shallClearResults=0; } - timer++; - vTaskDelay(1000/ portTICK_PERIOD_MS); + vTaskDelay(10000/ portTICK_PERIOD_MS); } vTaskDelete( NULL ); } void MI32StartSensorTask(){ MI32.mode.willConnect = 1; + if (MIBLEsensors[MI32.state.sensor].type != LYWSD03MMC) { + MI32.mode.willConnect = 0; + return; + } xTaskCreatePinnedToCore( MI32SensorTask, /* Function to implement the task */ "MI32SensorTask", /* Name of the task */ - 8192, /* Stack size in words */ + 4096, /* Stack size in words */ NULL, /* Task input parameter */ 15, /* Priority of the task */ NULL, /* Task handle. */ @@ -499,16 +694,12 @@ void MI32StartSensorTask(){ } void MI32SensorTask(void *pvParameters){ - if (MIBLEsensors[MI32.state.sensor].type != LYWSD03MMC) { - MI32.mode.willConnect = 0; - vTaskDelete( NULL ); - } if (MI32ConnectActiveSensor()){ uint32_t timer = 0; while (MI32.mode.connected == 0){ if (timer>1000){ MI32Client->disconnect(); - NimBLEDevice::deleteClient(MI32Client); + // NimBLEDevice::deleteClient(MI32Client); MI32.mode.willConnect = 0; vTaskDelay(100/ portTICK_PERIOD_MS); vTaskDelete( NULL ); @@ -537,7 +728,7 @@ void MI32SensorTask(void *pvParameters){ MI32Client->disconnect(); DEBUG_SENSOR_LOG(PSTR("%s: requested disconnect"),D_CMND_MI32); } - vTaskDelay(500/ portTICK_PERIOD_MS); + MI32.mode.connected = 0; vTaskDelete( NULL ); } @@ -566,7 +757,7 @@ void MI32StartTimeTask(){ xTaskCreatePinnedToCore( MI32TimeTask, /* Function to implement the task */ "MI32TimeTask", /* Name of the task */ - 8912, /* Stack size in words */ + 4096, /* Stack size in words */ NULL, /* Task input parameter */ 15, /* Priority of the task */ NULL, /* Task handle. */ @@ -621,8 +812,9 @@ void MI32TimeTask(void *pvParameters){ } MI32Client->disconnect(); } - vTaskDelay(500/ portTICK_PERIOD_MS); + MI32.mode.connected = 0; + MI32.mode.canScan = 1; vTaskDelete( NULL ); } @@ -631,7 +823,7 @@ void MI32StartUnitTask(){ xTaskCreatePinnedToCore( MI32UnitTask, /* Function to implement the task */ "MI32UnitTask", /* Name of the task */ - 8912, /* Stack size in words */ + 4096, /* Stack size in words */ NULL, /* Task input parameter */ 15, /* Priority of the task */ NULL, /* Task handle. */ @@ -658,8 +850,8 @@ void MI32UnitTask(void *pvParameters){ NimBLERemoteService* pSvc = nullptr; NimBLERemoteCharacteristic* pChr = nullptr; - static BLEUUID serviceUUID("EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6"); - static BLEUUID charUUID("EBE0CCBE-7A0A-4B0C-8A1A-6FF2997DA3A6"); + static BLEUUID serviceUUID(0xEBE0CCB0,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); + static BLEUUID charUUID(0xEBE0CCBE,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); pSvc = MI32Client->getService(serviceUUID); if(pSvc) { pChr = pSvc->getCharacteristic(charUUID); @@ -687,8 +879,9 @@ void MI32UnitTask(void *pvParameters){ } MI32Client->disconnect(); } - vTaskDelay(500/ portTICK_PERIOD_MS); + MI32.mode.connected = 0; + MI32.mode.canScan = 1; vTaskDelete( NULL ); } @@ -697,10 +890,18 @@ void MI32StartBatteryTask(){ MI32.mode.willReadBatt = 1; MI32.mode.willConnect = 1; MI32.mode.canScan = 0; + + switch (MIBLEsensors[MI32.state.sensor].type){ + case LYWSD03MMC: case MJ_HT_V1: case CGG1: case NLIGHT: case MJYD2S: case YEERC: + MI32.mode.willConnect = 0; + MI32.mode.willReadBatt = 0; + return; + } + xTaskCreatePinnedToCore( MI32BatteryTask, /* Function to implement the task */ "MI32BatteryTask", /* Name of the task */ - 8192, /* Stack size in words */ + 4096, /* Stack size in words */ NULL, /* Task input parameter */ 15, /* Priority of the task */ NULL, /* Task handle. */ @@ -709,15 +910,6 @@ void MI32StartBatteryTask(){ void MI32BatteryTask(void *pvParameters){ // all reported battery values are probably crap, but we allow the reading on demand - switch (MIBLEsensors[MI32.state.sensor].type){ - case LYWSD03MMC: case MJ_HT_V1: case CGG1: - MI32.mode.willConnect = 0; - MI32.mode.willReadBatt = 0; - vTaskDelete( NULL ); - break; - default: - break; - } MI32.mode.connected = 0; if(MI32ConnectActiveSensor()){ @@ -731,26 +923,18 @@ void MI32BatteryTask(void *pvParameters){ } switch(MIBLEsensors[MI32.state.sensor].type){ - case FLORA: - MI32batteryFLORA(); - break; - case LYWSD02: - MI32batteryLYWSD02(); - break; - case CGD1: - MI32batteryCGD1(); + case FLORA: case LYWSD02: case CGD1: + MI32batteryRead(MIBLEsensors[MI32.state.sensor].type); break; } MI32Client->disconnect(); } MI32.mode.willReadBatt = 0; - // Wifi.counter = 0; // Now check it - vTaskDelay(500/ portTICK_PERIOD_MS); MI32.mode.connected = 0; vTaskDelete( NULL ); } -void MI32batteryFLORA(){ +void MI32batteryRead(uint32_t _type){ uint32_t timer = 0; while (!MI32.mode.connected){ if (timer>1000){ @@ -759,79 +943,45 @@ void MI32batteryFLORA(){ timer++; vTaskDelay(10/ portTICK_PERIOD_MS); } - DEBUG_SENSOR_LOG(PSTR("%s connected for battery"),kMI32SlaveType[MIBLEsensors[MI32.state.sensor].type-1] ); + DEBUG_SENSOR_LOG(PSTR("%s connected for battery"),kMI32DeviceType[MIBLEsensors[MI32.state.sensor].type-1] ); NimBLERemoteService* pSvc = nullptr; NimBLERemoteCharacteristic* pChr = nullptr; - static BLEUUID FLserviceUUID(0x00001204,0x0000,0x1000,0x800000805f9b34fb); - static BLEUUID FLcharUUID(0x00001a02,0x0000,0x1000,0x800000805f9b34fb); - pSvc = MI32Client->getService(FLserviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(FLcharUUID); - } - if (pChr){ - DEBUG_SENSOR_LOG(PSTR("%s: got Flora char %s"),D_CMND_MI32, pChr->getUUID().toString().c_str()); - if(pChr->canRead()) { - const char *buf = pChr->readValue().c_str(); - MI32readBat((char*)buf); - //we also can read the firmware from response no extra request needed - MI32readFirmwareFLORA((char*)buf); - } - } - MI32.mode.readingDone = 1; -} - -void MI32batteryLYWSD02(){ - uint32_t timer = 0; - while (!MI32.mode.connected){ - if (timer>1000){ - break; + switch(_type){ + case FLORA: + { + static BLEUUID _serviceUUID(0x00001204,0x0000,0x1000,0x800000805f9b34fb); + static BLEUUID _charUUID(0x00001a02,0x0000,0x1000,0x800000805f9b34fb); + pSvc = MI32Client->getService(_serviceUUID); + if(pSvc) { + pChr = pSvc->getCharacteristic(_charUUID); } - timer++; - vTaskDelay(10/ portTICK_PERIOD_MS); } - - NimBLERemoteService* pSvc = nullptr; - NimBLERemoteCharacteristic* pChr = nullptr; - static BLEUUID LY2serviceUUID(0xEBE0CCB0,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - static BLEUUID LY2charUUID(0xEBE0CCC4,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - - pSvc = MI32Client->getService(LY2serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(LY2charUUID); - } - if (pChr){ - DEBUG_SENSOR_LOG( PSTR("%s: got LYWSD02 char %s"),D_CMND_MI32, pChr->getUUID().toString().c_str()); - if(pChr->canRead()) { - DEBUG_SENSOR_LOG(PSTR("LYWSD02 char")); - const char *buf = pChr->readValue().c_str(); - MI32readBat((char*)buf); - } - } - MI32.mode.readingDone = 1; -} - -void MI32batteryCGD1(){ - uint32_t timer = 0; - while (!MI32.mode.connected){ - if (timer>1000){ - break; + break; + case LYWSD02: + { + static BLEUUID _serviceUUID(0xEBE0CCB0,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); + static BLEUUID _charUUID(0xEBE0CCC4,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); + pSvc = MI32Client->getService(_serviceUUID); + if(pSvc) { + pChr = pSvc->getCharacteristic(_charUUID); } - timer++; - vTaskDelay(10/ portTICK_PERIOD_MS); } - - NimBLERemoteService* pSvc = nullptr; - NimBLERemoteCharacteristic* pChr = nullptr; - static BLEUUID CGD1serviceUUID((uint16_t)0x180F); - static BLEUUID CGD1charUUID((uint16_t)0x2A19); - - pSvc = MI32Client->getService(CGD1serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(CGD1charUUID); + break; + case CGD1: + { + static BLEUUID _serviceUUID((uint16_t)0x180F); + static BLEUUID _charUUID((uint16_t)0x2A19); + pSvc = MI32Client->getService(_serviceUUID); + if(pSvc) { + pChr = pSvc->getCharacteristic(_charUUID); + } + } + break; } + if (pChr){ - DEBUG_SENSOR_LOG(PSTR("%s: got CGD1 char %s"),D_CMND_MI32, pChr->getUUID().toString().c_str()); + DEBUG_SENSOR_LOG(PSTR("%s: got %s char %s"),D_CMND_MI32, kMI32DeviceType[MIBLEsensors[MI32.state.sensor].type-1], pChr->getUUID().toString().c_str()); if(pChr->canRead()) { const char *buf = pChr->readValue().c_str(); MI32readBat((char*)buf); @@ -840,87 +990,166 @@ void MI32batteryCGD1(){ MI32.mode.readingDone = 1; } - /*********************************************************************************************\ * parse the response from advertisements \*********************************************************************************************/ -void MI32parseMiBeacon(char * _buf, uint32_t _slot){ +void MI32parseMiBeacon(char * _buf, uint32_t _slot, uint16_t _bufSize){ float _tempFloat; mi_beacon_t _beacon; - if (MIBLEsensors[_slot].type==MJ_HT_V1 || MIBLEsensors[_slot].type==CGG1){ - memcpy((uint8_t*)&_beacon+1,(uint8_t*)_buf, sizeof(_beacon)); // shift by one byte for the MJ_HT_V1 - memcpy((uint8_t*)&_beacon.Mac,(uint8_t*)&_beacon.Mac+1,6); // but shift back the MAC + + if (MIBLEsensors[_slot].type==MJ_HT_V1 || MIBLEsensors[_slot].type==CGG1 || MIBLEsensors[_slot].type==YEERC){ + memcpy((uint8_t*)&_beacon+1,(uint8_t*)_buf, sizeof(_beacon)-1); // shift by one byte for the MJ_HT_V1 DANGER!!! + memcpy((uint8_t*)&_beacon.MAC,(uint8_t*)&_beacon.MAC+1,6); // but shift back the MAC + _beacon.counter = _buf[4]; // restore the counter } else{ - memcpy((void*)&_beacon,(void*)_buf, sizeof(_beacon)); + memcpy((char *)&_beacon, _buf, _bufSize); } - MI32_ReverseMAC(_beacon.Mac); - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MiBeacon type:%02x: %02x %02x %02x %02x %02x %02x %02x %02x"),_beacon.type, (uint8_t)_buf[0],(uint8_t)_buf[1],(uint8_t)_buf[2],(uint8_t)_buf[3],(uint8_t)_buf[4],(uint8_t)_buf[5],(uint8_t)_buf[6],(uint8_t)_buf[7]); - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR(" type:%02x: %02x %02x %02x %02x %02x %02x %02x %02x"),_beacon.type, (uint8_t)_buf[8],(uint8_t)_buf[9],(uint8_t)_buf[10],(uint8_t)_buf[11],(uint8_t)_buf[12],(uint8_t)_buf[13],(uint8_t)_buf[14],(uint8_t)_buf[15]); + MIBLEsensors[_slot].lastCnt = _beacon.counter; +#ifdef USE_MI_DECRYPTION + switch(MIBLEsensors[_slot].type){ + case LYWSD03MMC: + if (_beacon.frame == 0x5858){ + int decryptRet = MI32_decryptPacket((char*)&_beacon.productID,_bufSize, LYWSD03MMC); //start with PID + } + break; + case MJYD2S: + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MJYD2S: %x"),_beacon.frame); + if (_beacon.frame == 0x5948){ // Now let's build/recreate a special MiBeacon + memmove((uint8_t*)&_beacon.MAC+6,(uint8_t*)&_beacon.MAC, _bufSize); // shift payload by the size of the MAC = 6 bytes + memcpy((uint8_t*)&_beacon.MAC,MIBLEsensors[_slot].MAC,6); // now insert the real MAC from our internal vector + _bufSize+=6; // the packet has grown + MI32_ReverseMAC(_beacon.MAC); // payload MAC is always reversed + } + if (_beacon.frame != 0x5910){ + int decryptRet = MI32_decryptPacket((char*)&_beacon.productID,_bufSize,MJYD2S); //start with PID + } + else{ + if(millis()-MIBLEsensors[_slot].lastTime>120000){ + MIBLEsensors[_slot].eventType = 1; + MIBLEsensors[_slot].events++; + MIBLEsensors[_slot].shallSendMQTT = 1; + MIBLEsensors[_slot].lastTime = millis(); + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MI32: MJYD2S secondary PIR")); + MIBLEsensors[_slot].NMT = 0; + MI32triggerTele(); + } + } + break; + } +#endif //USE_MI_DECRYPTION - if(MIBLEsensors[_slot].type==4 || MIBLEsensors[_slot].type==6){ +if (MIBLEsensors[_slot].type==NLIGHT){ + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MiBeacon type:%02x: %02x %02x %02x %02x %02x %02x %02x %02x"),_beacon.type, (uint8_t)_buf[0],(uint8_t)_buf[1],(uint8_t)_buf[2],(uint8_t)_buf[3],(uint8_t)_buf[4],(uint8_t)_buf[5],(uint8_t)_buf[6],(uint8_t)_buf[7]); + AddLog_P2(LOG_LEVEL_DEBUG,PSTR(" type:%02x: %02x %02x %02x %02x %02x %02x %02x %02x"),_beacon.type, (uint8_t)_buf[8],(uint8_t)_buf[9],(uint8_t)_buf[10],(uint8_t)_buf[11],(uint8_t)_buf[12],(uint8_t)_buf[13],(uint8_t)_buf[14],(uint8_t)_buf[15]); +} + + if(MIBLEsensors[_slot].type==6){ DEBUG_SENSOR_LOG(PSTR("LYWSD03 and CGD1 no support for MiBeacon, type %u"),MIBLEsensors[_slot].type); return; } - AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32SlaveType[MIBLEsensors[_slot].type-1],_slot); + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); switch(_beacon.type){ + case 0x01: + MIBLEsensors[_slot].Btn=_beacon.Btn.num + (_beacon.Btn.longPress/2)*6; + MIBLEsensors[_slot].shallSendMQTT = 1; + MI32triggerTele(); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 1: U16: %u Button"), MIBLEsensors[_slot].Btn ); + break; case 0x04: - _tempFloat=(float)(_beacon.temp)/10.0f; - if(_tempFloat<60){ - MIBLEsensors[_slot].temp=_tempFloat; - DEBUG_SENSOR_LOG(PSTR("Mode 4: temp updated")); - } - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 4: U16: %u Temp"), _beacon.temp ); + _tempFloat=(float)(_beacon.temp)/10.0f; + if(_tempFloat<60){ + MIBLEsensors[_slot].temp=_tempFloat; + DEBUG_SENSOR_LOG(PSTR("Mode 4: temp updated")); + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 4: U16: %u Temp"), _beacon.temp ); break; case 0x06: - _tempFloat=(float)(_beacon.hum)/10.0f; - if(_tempFloat<101){ - MIBLEsensors[_slot].hum=_tempFloat; - DEBUG_SENSOR_LOG(PSTR("Mode 6: hum updated")); - } - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 6: U16: %u Hum"), _beacon.hum); + _tempFloat=(float)(_beacon.hum)/10.0f; + if(_tempFloat<101){ + MIBLEsensors[_slot].hum=_tempFloat; + DEBUG_SENSOR_LOG(PSTR("Mode 6: hum updated")); + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 6: U16: %u Hum"), _beacon.hum); break; case 0x07: - MIBLEsensors[_slot].lux=_beacon.lux & 0x00ffffff; - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 7: U24: %u Lux"), _beacon.lux & 0x00ffffff); + MIBLEsensors[_slot].lux=_beacon.lux & 0x00ffffff; + if(MIBLEsensors[_slot].type==MJYD2S){ + MIBLEsensors[_slot].eventType = 2; //No PIR + MIBLEsensors[_slot].shallSendMQTT = 1; + MIBLEsensors[_slot].lastTime = millis(); + MI32triggerTele(); + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 7: U24: %u Lux"), _beacon.lux & 0x00ffffff); break; case 0x08: - _tempFloat =(float)_beacon.moist; - if(_tempFloat<100){ - MIBLEsensors[_slot].moisture=_tempFloat; - DEBUG_SENSOR_LOG(PSTR("Mode 8: moisture updated")); - } - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 8: U8: %u Moisture"), _beacon.moist); + _tempFloat =(float)_beacon.moist; + if(_tempFloat<100){ + MIBLEsensors[_slot].moisture=_tempFloat; + DEBUG_SENSOR_LOG(PSTR("Mode 8: moisture updated")); + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 8: U8: %u Moisture"), _beacon.moist); break; case 0x09: - _tempFloat=(float)(_beacon.fert); - if(_tempFloat<65535){ // ??? - MIBLEsensors[_slot].fertility=_tempFloat; - DEBUG_SENSOR_LOG(PSTR("Mode 9: fertility updated")); - } - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 9: U16: %u Fertility"), _beacon.fert); + _tempFloat=(float)(_beacon.fert); + if(_tempFloat<65535){ // ??? + MIBLEsensors[_slot].fertility=_tempFloat; + DEBUG_SENSOR_LOG(PSTR("Mode 9: fertility updated")); + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 9: U16: %u Fertility"), _beacon.fert); break; case 0x0a: - if(_beacon.bat<101){ - MIBLEsensors[_slot].bat = _beacon.bat; - DEBUG_SENSOR_LOG(PSTR("Mode a: bat updated")); - } - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode a: U8: %u %%"), _beacon.bat); + if(_beacon.bat<101){ + MIBLEsensors[_slot].bat = _beacon.bat; + DEBUG_SENSOR_LOG(PSTR("Mode a: bat updated")); + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode a: U8: %u %%"), _beacon.bat); break; case 0x0d: - _tempFloat=(float)(_beacon.HT.temp)/10.0f; - if(_tempFloat<60){ - MIBLEsensors[_slot].temp = _tempFloat; - DEBUG_SENSOR_LOG(PSTR("Mode d: temp updated")); - } - _tempFloat=(float)(_beacon.HT.hum)/10.0f; - if(_tempFloat<100){ - MIBLEsensors[_slot].hum = _tempFloat; - DEBUG_SENSOR_LOG(PSTR("Mode d: hum updated")); - } - // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode d: U16: %x Temp U16: %x Hum"), _beacon.HT.temp, _beacon.HT.hum); + _tempFloat=(float)(_beacon.HT.temp)/10.0f; + if(_tempFloat<60){ + MIBLEsensors[_slot].temp = _tempFloat; + DEBUG_SENSOR_LOG(PSTR("Mode d: temp updated")); + } + _tempFloat=(float)(_beacon.HT.hum)/10.0f; + if(_tempFloat<100){ + MIBLEsensors[_slot].hum = _tempFloat; + DEBUG_SENSOR_LOG(PSTR("Mode d: hum updated")); + } + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode d: U16: %x Temp U16: %x Hum"), _beacon.HT.temp, _beacon.HT.hum); + break; +#ifdef USE_MI_DECRYPTION + case 0x0f: + if (_beacon.ten!=0) break; + MIBLEsensors[_slot].eventType = 1; //PIR + MIBLEsensors[_slot].shallSendMQTT = 1; + MIBLEsensors[_slot].lastTime = millis(); + MIBLEsensors[_slot].events++; + MIBLEsensors[_slot].lux = _beacon.lux; + MIBLEsensors[_slot].NMT = 0; + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("PIR: primary"),MIBLEsensors[_slot].lux ); + MI32triggerTele(); + break; + case 0x17: + MIBLEsensors[_slot].NMT = _beacon.NMT; + MIBLEsensors[_slot].eventType = 3; // NMT + MIBLEsensors[_slot].shallSendMQTT = 1; + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("Mode 17: NMT: %u seconds"), _beacon.NMT); + MI32triggerTele(); + break; +#endif //USE_MI_DECRYPTION + default: + if (MIBLEsensors[_slot].type==NLIGHT){ + MIBLEsensors[_slot].eventType = 1; //PIR + MIBLEsensors[_slot].shallSendMQTT = 1; + MIBLEsensors[_slot].events++; + MIBLEsensors[_slot].NMT = 0; + MIBLEsensors[_slot].lastTime = millis(); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("PIR: primary"),MIBLEsensors[_slot].lux ); + MI32triggerTele(); + } break; } } @@ -928,8 +1157,8 @@ void MI32parseMiBeacon(char * _buf, uint32_t _slot){ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6]){ // no MiBeacon uint8_t _addr[6]; memcpy(_addr,addr,6); - uint32_t _slot = MIBLEgetSensorSlot(_addr, 0x0576); // This must be hard-coded, no object-id in Cleargrass-packet - DEBUG_SENSOR_LOG(PSTR("MI32: Sensor slot: %u"), _slot); + uint32_t _slot = MIBLEgetSensorSlot(_addr, 0x0576, 0); // This must be hard-coded, no object-id in Cleargrass-packet, we have no packet counter too + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); if(_slot==0xff) return; cg_packet_t _packet; memcpy((char*)&_packet,_buf,sizeof(_packet)); @@ -960,16 +1189,15 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6]){ // no M } void MI32ParseResponse(char *buf, uint16_t bufsize, uint8_t addr[6]) { - if(bufsize<10) { + if(bufsize<9) { //9 is from the NLIGHT return; } - char * _pos = buf; - uint16_t _type= _pos[3]*256 + _pos[2]; + uint16_t _type= buf[3]*256 + buf[2]; // AddLog_P2(LOG_LEVEL_INFO, PSTR("%02x %02x %02x %02x"),(uint8_t)buf[0], (uint8_t)buf[1],(uint8_t)buf[2],(uint8_t)buf[3]); uint8_t _addr[6]; memcpy(_addr,addr,6); - uint16_t _slot = MIBLEgetSensorSlot(_addr, _type); - if(_slot!=0xff) MI32parseMiBeacon(_pos,_slot); + uint16_t _slot = MIBLEgetSensorSlot(_addr, _type, buf[4]); + if(_slot!=0xff) MI32parseMiBeacon(buf,_slot,bufsize); } /***********************************************************************\ @@ -988,7 +1216,7 @@ void MI32readHT_LY(char *_buf){ _tempFloat=(float)(LYWSD0x_HT.temp)/100.0f; if(_tempFloat<60){ MIBLEsensors[_slot].temp=_tempFloat; - MIBLEsensors[_slot].showedUp=255; // this sensor is real + // MIBLEsensors[_slot].showedUp=255; // this sensor is real } _tempFloat=(float)LYWSD0x_HT.hum; if(_tempFloat<100){ @@ -996,7 +1224,7 @@ void MI32readHT_LY(char *_buf){ DEBUG_SENSOR_LOG(PSTR("LYWSD0x: hum updated")); } if (MIBLEsensors[_slot].type == LYWSD03MMC){ - MIBLEsensors[_slot].volt = LYWSD0x_HT.volt; + MIBLEsensors[_slot].bat = ((float)LYWSD0x_HT.volt-2100.0f)/12.0f; } } } @@ -1009,29 +1237,17 @@ bool MI32readBat(char *_buf){ DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); if(_buf[0]<101){ MIBLEsensors[_slot].bat=_buf[0]; + if(MIBLEsensors[_slot].type==FLORA){ + memcpy(MIBLEsensors[_slot].firmware, _buf+2, 5); + MIBLEsensors[_slot].firmware[5] = '\0'; + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: Firmware: %s"),D_CMND_MI32,MIBLEsensors[_slot].firmware); + } return true; } } return false; } -bool MI32readFirmwareFLORA(char *_buf){ - DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_MI32,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); - if(_buf[0] != 0){ - char _firmware[5]; // FLORA send 5 byte for firmware version - strncpy(_firmware, _buf+2, 5); - AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: Firmware: %s"),D_CMND_MI32,_firmware); - - uint32_t _slot = MI32.state.sensor; - DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); - - memcpy(MIBLEsensors[_slot].firmware, _firmware, 5); - MIBLEsensors[_slot].firmware[5] = '\0'; - return true; - } - return false; -} - /** * @brief Main loop of the driver, "high level"-loop * @@ -1041,6 +1257,12 @@ void MI32EverySecond(bool restart){ static uint32_t _counter = MI32.period - 15; static uint32_t _nextSensorSlot = 0; + for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { + if(MIBLEsensors[i].type==NLIGHT || MIBLEsensors[i].type==MJYD2S){ + MIBLEsensors[i].NMT++; + } + } + if(restart){ _counter = 0; MI32.mode.canScan = 0; @@ -1084,22 +1306,25 @@ void MI32EverySecond(bool restart){ } if(_counter==0) { + MI32.state.sensor = _nextSensorSlot; AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: active sensor now: %u of %u"),D_CMND_MI32, MI32.state.sensor, MIBLEsensors.size()-1); MI32.mode.canScan = 0; - if (MI32.mode.runningScan|| MI32.mode.connected || MI32.mode.willConnect) return; + // if (MI32.mode.runningScan|| MI32.mode.connected || MI32.mode.willConnect) return; + if (MI32.mode.connected || MI32.mode.willConnect) return; _nextSensorSlot++; MI32.mode.canConnect = 1; if(MI32.mode.connected == 0) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("will connect to %s"),kMI32SlaveType[MIBLEsensors[MI32.state.sensor].type-1] ); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("will connect to %s"),kMI32DeviceType[MIBLEsensors[MI32.state.sensor].type-1] ); if (MI32.mode.shallReadBatt) { MI32StartTask(MI32_TASK_BATT); - } + } +#ifndef USE_MI_DECRYPTION // turn off connections, because we only listen to advertisements else{ MI32StartTask(MI32_TASK_CONN); - } - + } +#endif //USE_MI_DECRYPTION } if (_nextSensorSlot>(MIBLEsensors.size()-1)) { _nextSensorSlot= 0; @@ -1191,6 +1416,14 @@ bool MI32Cmd(void) { XdrvMailbox.payload = MI32.period; Response_P(S_JSON_MI32_COMMAND, command, ""); break; +#ifdef USE_MI_DECRYPTION + case CMND_MI32_KEY: + if (XdrvMailbox.data_len==44){ // a KEY-MAC-string + MI32AddKey(XdrvMailbox.data); + Response_P(S_JSON_MI32_COMMAND, command, XdrvMailbox.data); + } + break; +#endif //USE_MI_DECRYPTION default: // else for Unknown command serviced = false; @@ -1211,23 +1444,36 @@ const char HTTP_MI32[] PROGMEM = "{s}MI ESP32 {m}%u%s / %u{e}"; const char HTTP_MI32_SERIAL[] PROGMEM = "{s}%s %s{m}%02x:%02x:%02x:%02x:%02x:%02x%{e}"; const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u %%{e}"; const char HTTP_VOLTAGE[] PROGMEM = "{s}%s " D_VOLTAGE "{m}%s V{e}"; +const char HTTP_LASTBUTTON[] PROGMEM = "{s}%s Last Button{m}%u {e}"; +const char HTTP_EVENTS[] PROGMEM = "{s}%s Events{m}%u {e}"; +const char HTTP_NMT[] PROGMEM = "{s}%s No motion{m}> %u seconds{e}"; const char HTTP_MI32_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%u us/cm{e}"; const char HTTP_MI32_HL[] PROGMEM = "{s}
{m}
{e}"; void MI32Show(bool json) { - if (json) { + if(!MI32.mode.triggeredTele){ + MI32.mode.shallClearResults=1; + } for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { + switch(MIBLEsensors[i].type){ + case YEERC: + if(MIBLEsensors[i].shallSendMQTT==0) continue; + break; + default: + if(MI32.mode.triggeredTele) continue; + break; + } /* char slave[33]; snprintf_P(slave, sizeof(slave), PSTR("%s-%02x%02x%02x"), - kMI32SlaveType[MIBLEsensors[i].type-1],MIBLEsensors[i].serial[3],MIBLEsensors[i].serial[4],MIBLEsensors[i].serial[5]); + kMI32DeviceType[MIBLEsensors[i].type-1],MIBLEsensors[i].serial[3],MIBLEsensors[i].serial[4],MIBLEsensors[i].serial[5]); ResponseAppend_P(PSTR(",\"%s\":{"), slave); */ ResponseAppend_P(PSTR(",\"%s-%02x%02x%02x\":{"), - kMI32SlaveType[MIBLEsensors[i].type-1], - MIBLEsensors[i].serial[3], MIBLEsensors[i].serial[4], MIBLEsensors[i].serial[5]); + kMI32DeviceType[MIBLEsensors[i].type-1], + MIBLEsensors[i].MAC[3], MIBLEsensors[i].MAC[4], MIBLEsensors[i].MAC[5]); if (MIBLEsensors[i].type == FLORA) { if (!isnan(MIBLEsensors[i].temp)) { @@ -1247,25 +1493,35 @@ void MI32Show(bool json) if (!isnan(MIBLEsensors[i].fertility)) { ResponseAppend_P(PSTR(",\"Fertility\":%f"), MIBLEsensors[i].fertility); } + ResponseAppend_P(PSTR(",\"Firmware\":\"%s\""), MIBLEsensors[i].firmware); } if (MIBLEsensors[i].type > FLORA){ if (!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp)) { ResponseAppendTHD(MIBLEsensors[i].temp, MIBLEsensors[i].hum); } } +#ifdef USE_MI_DECRYPTION + if (MIBLEsensors[i].type == MJYD2S){ + ResponseAppend_P(PSTR("\"Events\":%u"),MIBLEsensors[i].events); + if(MIBLEsensors[i].shallSendMQTT && MIBLEsensors[i].eventType<3) ResponseAppend_P(PSTR(",\"PIR\":%u"), 2 - MIBLEsensors[i].eventType); + if(MIBLEsensors[i].eventType==3) ResponseAppend_P(PSTR(",\"NMT\":%u"), MIBLEsensors[i].NMT); + MIBLEsensors[i].eventType=0; + if(MIBLEsensors[i].lux!=0x0ffffff) ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%u"), MIBLEsensors[i].lux); + } +#endif //USE_MI_DECRYPTION + if (MIBLEsensors[i].type == NLIGHT){ + ResponseAppend_P(PSTR("\"Events\":%u"),MIBLEsensors[i].events); + if(MIBLEsensors[i].shallSendMQTT) ResponseAppend_P(PSTR(",\"PIR\":1")); + } + if (MIBLEsensors[i].type == YEERC){ + if(MIBLEsensors[i].shallSendMQTT) ResponseAppend_P(PSTR("\"Btn\":%u"),MIBLEsensors[i].Btn); + } if (MIBLEsensors[i].bat != 0x00) { // this is the error code -> no battery - if (MIBLEsensors[i].type != LYWSD03MMC) { - ResponseAppend_P(PSTR(",\"Battery\":%u"), MIBLEsensors[i].bat); - } else { - char voltage[FLOATSZ]; - dtostrfd((MIBLEsensors[i].volt)/1000.0f, Settings.flag2.voltage_resolution, voltage); - ResponseAppend_P(PSTR(",\"" D_VOLTAGE "\":%s"), voltage); - } - if (MIBLEsensors[i].type == FLORA) { //actually we can only read FLORA - ResponseAppend_P(PSTR(",\"Firmware\":\"%s\""), MIBLEsensors[i].firmware); - } + ResponseAppend_P(PSTR(",\"Battery\":%u"), MIBLEsensors[i].bat); } ResponseAppend_P(PSTR("}")); + MIBLEsensors[i].shallSendMQTT = 0; + MI32.mode.triggeredTele = 0; } #ifdef USE_WEBSERVER } else { @@ -1285,36 +1541,41 @@ void MI32Show(bool json) WSContentSend_PD(HTTP_MI32, i+1,stemp,MIBLEsensors.size()); for (i; i no valid value - WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kMI32SlaveType[MIBLEsensors[i].type-1], MIBLEsensors[i].lux); + WSContentSend_PD(HTTP_SNS_TEMP, kMI32DeviceType[MIBLEsensors[i].type-1], temperature, TempUnit()); } if (!isnan(MIBLEsensors[i].moisture)) { - WSContentSend_PD(HTTP_SNS_MOISTURE, kMI32SlaveType[MIBLEsensors[i].type-1], int(MIBLEsensors[i].moisture)); + WSContentSend_PD(HTTP_SNS_MOISTURE, kMI32DeviceType[MIBLEsensors[i].type-1], int(MIBLEsensors[i].moisture)); } if (!isnan(MIBLEsensors[i].fertility)) { - WSContentSend_PD(HTTP_MI32_FLORA_DATA, kMI32SlaveType[MIBLEsensors[i].type-1], int(MIBLEsensors[i].fertility)); + WSContentSend_PD(HTTP_MI32_FLORA_DATA, kMI32DeviceType[MIBLEsensors[i].type-1], int(MIBLEsensors[i].fertility)); } } if (MIBLEsensors[i].type>FLORA) { // everything "above" Flora if (!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp)) { - WSContentSend_THD(kMI32SlaveType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp, MIBLEsensors[i].hum); + WSContentSend_THD(kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp, MIBLEsensors[i].hum); } } +#ifdef USE_MI_DECRYPTION + if (MIBLEsensors[i].type==NLIGHT || MIBLEsensors[i].type==MJYD2S) { +#else + if (MIBLEsensors[i].type==NLIGHT) { +#endif //USE_MI_DECRYPTION + WSContentSend_PD(HTTP_EVENTS, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].events); + if(MIBLEsensors[i].NMT>0) WSContentSend_PD(HTTP_NMT, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].NMT); + } + if (MIBLEsensors[i].lux!=0x00ffffff) { // this is the error code -> no valid value + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].lux); + } if(MIBLEsensors[i].bat!=0x00){ - if (MIBLEsensors[i].type != LYWSD03MMC) { - WSContentSend_PD(HTTP_BATTERY, kMI32SlaveType[MIBLEsensors[i].type-1], MIBLEsensors[i].bat); - } else { - char voltage[FLOATSZ]; - dtostrfd((MIBLEsensors[i].volt)/1000.0f, Settings.flag2.voltage_resolution, voltage); - WSContentSend_PD(HTTP_VOLTAGE, kMI32SlaveType[MIBLEsensors[i].type-1], voltage); - } + WSContentSend_PD(HTTP_BATTERY, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].bat); + } + if (MIBLEsensors[i].type==YEERC){ + WSContentSend_PD(HTTP_LASTBUTTON, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].Btn); } } _counter++; @@ -1360,4 +1621,4 @@ bool Xsns62(uint8_t function) return result; } #endif // USE_MI_ESP32 -#endif // ESP32 \ No newline at end of file +#endif // ESP32 From 96d37ec535846232e63f7ecf09d1b4e3849255b5 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 12 Jul 2020 18:53:57 +0200 Subject: [PATCH 13/13] Add Rotary features Add On/Off functionality to rotary dial (#8263) --- tasmota/support_button.ino | 2 +- tasmota/support_rotary.ino | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tasmota/support_button.ino b/tasmota/support_button.ino index 2d7c6a415..e6e6e27ea 100644 --- a/tasmota/support_button.ino +++ b/tasmota/support_button.ino @@ -303,7 +303,7 @@ void ButtonHandler(void) } } #ifdef ROTARY_V1 - if (!((0 == button_index) && RotaryButtonPressed())) { + if (!RotaryButtonPressed(button_index)) { #endif if (!Settings.flag3.mqtt_buttons && single_press && SendKey(KEY_BUTTON, button_index + Button.press_counter[button_index], POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set // Success diff --git a/tasmota/support_rotary.ino b/tasmota/support_rotary.ino index cfe549a5f..3367b70d4 100644 --- a/tasmota/support_rotary.ino +++ b/tasmota/support_rotary.ino @@ -91,13 +91,6 @@ struct ROTARY { void update_rotary(void) ICACHE_RAM_ATTR; void update_rotary(void) { if (Rotary.busy) { return; } - bool powered_on = (power); -#ifdef USE_LIGHT - if (!Settings.flag4.rotary_uses_rules) { // SetOption98 - Use rules instead of light control - powered_on = (LightPowerIRAM()); - } -#endif // USE_LIGHT - if (!powered_on) { return; } #ifdef ROTARY_OPTION1 // https://github.com/PaulStoffregen/Encoder/blob/master/Encoder.h @@ -212,8 +205,10 @@ void update_rotary(void) { #endif // ROTARY_OPTION3 } -bool RotaryButtonPressed(void) { +//bool RotaryButtonPressed(void) { +bool RotaryButtonPressed(uint32_t button_index) { if (!Rotary.present) { return false; } + if (0 != button_index) { return false; } bool powered_on = (power); #ifdef USE_LIGHT @@ -254,6 +249,13 @@ void RotaryHandler(void) { Rotary.timeout--; if (!Rotary.timeout) { Rotary.direction = 0; +#ifdef USE_LIGHT + if (!Settings.flag4.rotary_uses_rules) { // SetOption98 - Use rules instead of light control + LightState(0); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER)); + XdrvRulesProcess(); + } +#endif // USE_LIGHT } } if (Rotary.last_position == Rotary.position) { return; }