/*
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 TMSBSIZ52 512
#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];
char UID[32];
char MAJOR[4];
char MINOR[4];
uint8_t FLAGS;
uint8_t TIME;
} ibeacons[MAX_IBEACONS];
void IBEACON_Init() {
hm17_found=0;
// actually doesnt work reliably with software serial
if (PinUsed(GPIO_IBEACON_RX) && PinUsed(GPIO_IBEACON_TX)) {
IBEACON_Serial = new TasmotaSerial(Pin(GPIO_IBEACON_RX), Pin(GPIO_IBEACON_TX),1,0,TMSBSIZ52);
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 && (TasmotaGlobal.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",ibeacons[cnt].UID,ibeacons[cnt].MAJOR,ibeacons[cnt].MINOR);
}
}
}
} else {
if (TasmotaGlobal.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) {
/* if (!strncmp(ib->MAJOR,"4B1C",4)) {
return 0;
}
*/
if (!strncmp(ib->RSSI,"0",1)) {
return 0;
}
// keyfob starts with ffff, ibeacon has valid facid
if (!strncmp(ib->MAC,"FFFF",4) || strncmp(ib->FACID,"00000000",8)) {
for (uint32_t cnt=0;cntUID,PSTR("00000000000000000000000000000000"),32)) {
if (!strncmp(ibeacons[cnt].MAC,ib->MAC,12)) {
// exists
memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
ibeacons[cnt].TIME=0;
return 1;
}
} else {
if (!strncmp(ibeacons[cnt].UID,ib->UID,32)) {
// 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);
memcpy(ibeacons[cnt].UID,ib->UID,32);
memcpy(ibeacons[cnt].MAJOR,ib->MAJOR,4);
memcpy(ibeacons[cnt].MINOR,ib->MINOR,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,ib.UID,ib.MAJOR,ib.MINOR);
}
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_mac[] PROGMEM =
"{s}IBEACON-MAC : %s" " - RSSI : %s" "{m}{e}";
const char HTTP_IBEACON_uid[] PROGMEM =
"{s}IBEACON-UID : %s" " - RSSI : %s" "{m}{e}";
void IBEACON_Show(void) {
char mac[14];
char rssi[6];
char uid[34];
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