/* xsns_52_ibeacon.ino - Support for HM17 BLE Module + ibeacon reader on Tasmota Copyright (C) 2020 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 // use this for Version 110 #define HM17_V110 // 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+DISIS",8)) { hm17_sbclr(); hm17_result=1; #ifdef IBEACON_DEBUG if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISIS 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_cmd==HM17_DISI) { #ifdef HM17_V110 goto hm17_v110; #endif } else { 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)) { hm17_v110: 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); Response_P(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; Response_P(S_JSON_IBEACON1, XSNS_52,"hm17cmd",cp); } else if (*cp=='u') { cp++; if (*cp) IB_UPDATE_TIME=atoi(cp); Response_P(S_JSON_IBEACON, XSNS_52,"uintv",IB_UPDATE_TIME); } else if (*cp=='t') { cp++; if (*cp) IB_TIMEOUT_TIME=atoi(cp); Response_P(S_JSON_IBEACON, XSNS_52,"lintv",IB_TIMEOUT_TIME); } else if (*cp=='c') { for (uint32_t cnt=0;cnt