diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h
index b58e49f60..33ba9327e 100644
--- a/sonoff/my_user_config.h
+++ b/sonoff/my_user_config.h
@@ -420,6 +420,7 @@
// #define USE_PN532_DATA_FUNCTION // Add sensor40 command support for erase, setting data block content (+1k7 code, 388 bytes mem)
// #define USE_PN532_DATA_RAW // Allow DATA block to be used by non-alpha-numberic data (+ 80 bytes code, 48 bytes ram)
//#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)
// Power monitoring sensors -----------------------
#define USE_ENERGY_MARGIN_DETECTION // Add support for Energy Margin detection (+1k6 code)
diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h
index 41b97eb40..2d763cb47 100644
--- a/sonoff/sonoff_template.h
+++ b/sonoff/sonoff_template.h
@@ -192,6 +192,8 @@ enum UserSelectablePins {
GPIO_ZIGBEE_TX, // Zigbee Serial interface
GPIO_ZIGBEE_RX, // Zigbee Serial interface
GPIO_RDM6300_RX, // RDM6300 RX
+ GPIO_IBEACON_TX, // HM17 IBEACON TX
+ GPIO_IBEACON_RX, // HM17 IBEACON RX
GPIO_SENSOR_END };
// Programmer selectable GPIO functionality
@@ -264,6 +266,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_SOLAXX1_TX "|" D_SENSOR_SOLAXX1_RX "|"
D_SENSOR_ZIGBEE_TXD "|" D_SENSOR_ZIGBEE_RXD "|"
D_SENSOR_RDM6300_RX "|"
+ D_SENSOR_IBEACON_TX "|" D_SENSOR_IBEACON_RX "|"
;
// User selectable ADC0 functionality
@@ -667,6 +670,10 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_SOLAXX1_TX, // Solax Inverter tx pin
GPIO_SOLAXX1_RX, // Solax Inverter rx pin
#endif
+#ifdef USE_IBEACON
+ GPIO_IBEACON_RX,
+ GPIO_IBEACON_TX,
+#endif
};
const uint8_t kModuleNiceList[] PROGMEM = {
diff --git a/sonoff/xsns_52_ibeacon.ino b/sonoff/xsns_52_ibeacon.ino
new file mode 100644
index 000000000..89638a237
--- /dev/null
+++ b/sonoff/xsns_52_ibeacon.ino
@@ -0,0 +1,591 @@
+/*
+ xsns_52_ibeacon.ino - Support for HM17 BLE Module + ibeacon reader
+
+ Copyright (C) 2019 Gerhard Mutz and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_IBEACON
+
+
+
+#define XSNS_52 52
+
+#include
+
+#define HM17_BAUDRATE 9600
+
+//#define IBEACON_DEBUG
+
+// keyfob expires after N seconds
+#define IB_TIMEOUT_INTERVAL 30
+// does a passive scan every N seconds
+#define IB_UPDATE_TIME_INTERVAL 10
+
+TasmotaSerial *IBEACON_Serial = nullptr;
+
+
+uint8_t hm17_found,hm17_cmd,hm17_flag;
+
+#ifdef IBEACON_DEBUG
+uint8_t hm17_debug=0;
+#endif
+
+
+// 78 is max serial response
+#define HM17_BSIZ 128
+char hm17_sbuffer[HM17_BSIZ];
+uint8_t hm17_sindex,hm17_result,hm17_scanning,hm17_connecting;
+uint32_t hm17_lastms;
+char ib_mac[14];
+
+// should be in Settings
+#if 1
+uint8_t ib_upd_interval,ib_tout_interval;
+#define IB_UPDATE_TIME ib_upd_interval
+#define IB_TIMEOUT_TIME ib_tout_interval
+#else
+#undef IB_UPDATE_TIME
+#undef IB_TIMEOUT_TIME
+#define IB_UPDATE_TIME Settings.ib_upd_interval
+#define IB_TIMEOUT_TIME Settings.ib_tout_interval
+#endif
+
+enum {HM17_TEST,HM17_ROLE,HM17_IMME,HM17_DISI,HM17_IBEA,HM17_SCAN,HM17_DISC,HM17_RESET,HM17_RENEW,HM17_CON};
+#define HM17_SUCESS 99
+
+struct IBEACON {
+ char FACID[8];
+ char UID[32];
+ char MAJOR[4];
+ char MINOR[4];
+ char PWR[2];
+ char MAC[12];
+ char RSSI[4];
+};
+
+#define MAX_IBEACONS 16
+
+struct IBEACON_UID {
+ char MAC[12];
+ char RSSI[4];
+ uint8_t FLAGS;
+ uint8_t TIME;
+} ibeacons[MAX_IBEACONS];
+
+
+void IBEACON_Init() {
+
+ hm17_found=0;
+
+// actually doesnt work reliably with software serial
+ if ((pin[GPIO_IBEACON_RX] < 99) && (pin[GPIO_IBEACON_TX] < 99)) {
+ IBEACON_Serial = new TasmotaSerial(pin[GPIO_IBEACON_RX], pin[GPIO_IBEACON_TX],1);
+ if (IBEACON_Serial->begin(HM17_BAUDRATE)) {
+ if (IBEACON_Serial->hardwareSerial()) {
+ ClaimSerial();
+ }
+ hm17_sendcmd(HM17_TEST);
+ hm17_lastms=millis();
+ // in case of using Settings this has to be moved
+ IB_UPDATE_TIME=IB_UPDATE_TIME_INTERVAL;
+ IB_TIMEOUT_TIME=IB_TIMEOUT_INTERVAL;
+ }
+ }
+}
+
+void hm17_every_second(void) {
+ if (!IBEACON_Serial) return;
+
+ if (hm17_found) {
+ if (IB_UPDATE_TIME && (uptime%IB_UPDATE_TIME==0)) {
+ if (hm17_cmd!=99) {
+ if (hm17_flag&2) {
+ ib_sendbeep();
+ } else {
+ if (!hm17_connecting) {
+ hm17_sendcmd(HM17_DISI);
+ }
+ }
+ }
+ }
+ for (uint32_t cnt=0;cntIB_TIMEOUT_TIME) {
+ ibeacons[cnt].FLAGS=0;
+ ibeacon_mqtt(ibeacons[cnt].MAC,"0000");
+ }
+ }
+ }
+ } else {
+ if (uptime%20==0) {
+ hm17_sendcmd(HM17_TEST);
+ }
+ }
+}
+
+void hm17_sbclr(void) {
+ memset(hm17_sbuffer,0,HM17_BSIZ);
+ hm17_sindex=0;
+ IBEACON_Serial->flush();
+}
+
+void hm17_sendcmd(uint8_t cmd) {
+ hm17_sbclr();
+ hm17_cmd=cmd;
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("hm17cmd %d"),cmd);
+#endif
+ switch (cmd) {
+ case HM17_TEST:
+ IBEACON_Serial->write("AT");
+ break;
+ case HM17_ROLE:
+ IBEACON_Serial->write("AT+ROLE1");
+ break;
+ case HM17_IMME:
+ IBEACON_Serial->write("AT+IMME1");
+ break;
+ case HM17_DISI:
+ IBEACON_Serial->write("AT+DISI?");
+ hm17_scanning=1;
+ break;
+ case HM17_IBEA:
+ IBEACON_Serial->write("AT+IBEA1");
+ break;
+ case HM17_RESET:
+ IBEACON_Serial->write("AT+RESET");
+ break;
+ case HM17_RENEW:
+ IBEACON_Serial->write("AT+RENEW");
+ break;
+ case HM17_SCAN:
+ IBEACON_Serial->write("AT+SCAN5");
+ break;
+ case HM17_DISC:
+ IBEACON_Serial->write("AT+DISC?");
+ hm17_scanning=1;
+ break;
+ case HM17_CON:
+ IBEACON_Serial->write((const uint8_t*)"AT+CON",6);
+ IBEACON_Serial->write((const uint8_t*)ib_mac,12);
+ hm17_connecting=1;
+ break;
+ }
+}
+
+uint32_t ibeacon_add(struct IBEACON *ib) {
+ // keyfob starts with ffff, ibeacon has valid facid
+ if (!strncmp(ib->MAC,"FFFF",4) || strncmp(ib->FACID,"00000000",8)) {
+ for (uint32_t cnt=0;cntMAC,12)) {
+ // exists
+ memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
+ ibeacons[cnt].TIME=0;
+ return 1;
+ }
+ }
+ }
+ for (uint32_t cnt=0;cntMAC,12);
+ memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
+ ibeacons[cnt].FLAGS=1;
+ ibeacons[cnt].TIME=0;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+void hm17_decode(void) {
+ struct IBEACON ib;
+ switch (hm17_cmd) {
+ case HM17_TEST:
+ if (!strncmp(hm17_sbuffer,"OK",2)) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("AT OK"));
+#endif
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+ hm17_found=1;
+ }
+ break;
+ case HM17_ROLE:
+ if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("ROLE OK"));
+#endif
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+ }
+ break;
+ case HM17_IMME:
+ if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("IMME OK"));
+#endif
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+ }
+ break;
+ case HM17_IBEA:
+ if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("IBEA OK"));
+#endif
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+ }
+ break;
+ case HM17_SCAN:
+ if (!strncmp(hm17_sbuffer,"OK+Set:5",8)) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("SCAN OK"));
+#endif
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+ }
+ break;
+ case HM17_RESET:
+ if (!strncmp(hm17_sbuffer,"OK+RESET",8)) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("RESET OK"));
+#endif
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+ }
+ break;
+ case HM17_RENEW:
+ if (!strncmp(hm17_sbuffer,"OK+RENEW",8)) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("RENEW OK"));
+#endif
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+ }
+ break;
+ case HM17_CON:
+ if (!strncmp(hm17_sbuffer,"OK+CONNA",8)) {
+ hm17_sbclr();
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNA OK"));
+#endif
+ hm17_connecting=2;
+ break;
+ }
+ if (!strncmp(hm17_sbuffer,"OK+CONNE",8)) {
+ hm17_sbclr();
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNE ERROR"));
+#endif
+ break;
+ }
+ if (!strncmp(hm17_sbuffer,"OK+CONNF",8)) {
+ hm17_sbclr();
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNF ERROR"));
+#endif
+ break;
+ }
+ if (hm17_connecting==2 && !strncmp(hm17_sbuffer,"OK+CONN",7)) {
+ hm17_sbclr();
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONN OK"));
+#endif
+ hm17_connecting=3;
+ hm17_sendcmd(HM17_TEST);
+ hm17_connecting=0;
+ break;
+ }
+ break;
+
+ case HM17_DISI:
+ case HM17_DISC:
+ if (!strncmp(hm17_sbuffer,"OK+DISCS",8)) {
+ hm17_sbclr();
+ hm17_result=1;
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISCS OK"));
+#endif
+ break;
+ }
+ if (!strncmp(hm17_sbuffer,"OK+DISCE",8)) {
+ hm17_sbclr();
+ hm17_result=HM17_SUCESS;
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISCE OK"));
+#endif
+ hm17_scanning=0;
+ break;
+ }
+ if (!strncmp(hm17_sbuffer,"OK+NAME:",8)) {
+ if (hm17_sbuffer[hm17_sindex-1]=='\n') {
+ hm17_result=HM17_SUCESS;
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) {
+ AddLog_P2(LOG_LEVEL_INFO, PSTR("NAME OK"));
+ AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
+ }
+#endif
+ hm17_sbclr();
+ }
+ break;
+ }
+ if (!strncmp(hm17_sbuffer,"OK+DIS0:",8)) {
+ if (hm17_sindex==20) {
+ hm17_result=HM17_SUCESS;
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) {
+ AddLog_P2(LOG_LEVEL_INFO, PSTR("DIS0 OK"));
+ AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
+ }
+#endif
+ hm17_sbclr();
+ }
+ break;
+ }
+ if (!strncmp(hm17_sbuffer,"OK+DISC:",8)) {
+ if (hm17_cmd==HM17_DISI) {
+ if (hm17_sindex==78) {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) {
+ AddLog_P2(LOG_LEVEL_INFO, PSTR("DISC: OK"));
+ //OK+DISC:4C 000C0E:003 A9144081A8 3B16849611 862EC1005: 0B1CE7485D :4DB4E940F C0E:-078
+ AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
+ }
+#endif
+ memcpy(ib.FACID,&hm17_sbuffer[8],8);
+ memcpy(ib.UID,&hm17_sbuffer[8+8+1],32);
+ memcpy(ib.MAJOR,&hm17_sbuffer[8+8+1+32+1],4);
+ memcpy(ib.MINOR,&hm17_sbuffer[8+8+1+32+1+4],4);
+ memcpy(ib.PWR,&hm17_sbuffer[8+8+1+32+1+4+4],2);
+ memcpy(ib.MAC,&hm17_sbuffer[8+8+1+32+1+4+4+2+1],12);
+ memcpy(ib.RSSI,&hm17_sbuffer[8+8+1+32+1+4+4+2+1+12+1],4);
+
+ if (ibeacon_add(&ib)) {
+ ibeacon_mqtt(ib.MAC,ib.RSSI);
+ }
+ hm17_sbclr();
+ hm17_result=1;
+ }
+ } else {
+#ifdef IBEACON_DEBUG
+ if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
+#endif
+ }
+ break;
+ }
+ }
+}
+
+void IBEACON_loop() {
+
+ if (!IBEACON_Serial) return;
+
+uint32_t difftime=millis()-hm17_lastms;
+
+ while (IBEACON_Serial->available()) {
+ hm17_lastms=millis();
+ // shift in
+ if (hm17_sindexread();
+ hm17_sindex++;
+ hm17_decode();
+ } else {
+ hm17_sindex=0;
+ break;
+ }
+ }
+
+ if (hm17_cmd==99) {
+ if (hm17_sindex>=HM17_BSIZ-2 || (hm17_sindex && (difftime>100))) {
+ AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),hm17_sbuffer);
+ hm17_sbclr();
+ }
+ }
+
+}
+
+#ifdef USE_WEBSERVER
+const char HTTP_IBEACON[] PROGMEM =
+ "{s}IBEACON-UID : %s" " - RSSI : %s" "{m}{e}";
+
+void IBEACON_Show(void) {
+char mac[14];
+char rssi[6];
+
+ for (uint32_t cnt=0;cnt 0) {
+ char *cp=XdrvMailbox.data;
+ if (*cp>='0' && *cp<='8') {
+ hm17_sendcmd(*cp&7);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_IBEACON, XSNS_52,"hm17cmd",*cp&7);
+ } else if (*cp=='s') {
+ cp++;
+ len--;
+ while (*cp==' ') {
+ len--;
+ cp++;
+ }
+ IBEACON_Serial->write((uint8_t*)cp,len);
+ hm17_cmd=99;
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_IBEACON1, XSNS_52,"hm17cmd",cp);
+ } else if (*cp=='u') {
+ cp++;
+ if (*cp) IB_UPDATE_TIME=atoi(cp);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_IBEACON, XSNS_52,"uintv",IB_UPDATE_TIME);
+ } else if (*cp=='t') {
+ cp++;
+ if (*cp) IB_TIMEOUT_TIME=atoi(cp);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_IBEACON, XSNS_52,"lintv",IB_TIMEOUT_TIME);
+ } else if (*cp=='c') {
+ for (uint32_t cnt=0;cnt