Files
OpenMQTT-Gateway/main/ZgatewayBT.ino
T
Florian db8df1ddf6 Hass discovery improvments (#512)
Rename set commands to config
correct json buffer size and gateway name
replace state to cmd
replace set by config
add restart and erase command
add unicity to gateway command switches
BLE devices shouldn't be child of the gateway
make the BTtoMQTT topic consistent with other definitions
2019-12-20 23:01:56 +01:00

1245 lines
42 KiB
Arduino

/*
OpenMQTTGateway - ESP8266 or Arduino program for home automation
Act as a wifi or ethernet gateway between your 433mhz/infrared IR signal/BLE and a MQTT broker
Send and receiving command by MQTT
This gateway enables to:
- publish MQTT data to a different topic related to BLE devices rssi signal
- publish MQTT data related to mi flora temperature, moisture, fertility and lux
- publish MQTT data related to mi jia indoor temperature & humidity sensor
Copyright: (c)Florian ROBERT
This file is part of OpenMQTTGateway.
OpenMQTTGateway 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.
OpenMQTTGateway 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 <http://www.gnu.org/licenses/>.
Thanks to wolass https://github.com/wolass for suggesting me HM 10 and dinosd https://github.com/dinosd/BLE_PROXIMITY for inspiring me how to implement the gateway
*/
#include "User_config.h"
#ifdef ZgatewayBT
#include <vector>
using namespace std;
vector<BLEdevice> devices;
void setWorBMac(char *mac, bool isWhite)
{
bool foundMac = false;
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p)
{
if ((strcmp(p->macAdr, mac) == 0))
{
p->isWhtL = isWhite;
p->isBlkL = !isWhite;
foundMac = true;
}
}
if (!foundMac)
{
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = false;
device.isWhtL = isWhite;
device.isBlkL = !isWhite;
devices.push_back(device);
}
}
bool oneWhite()
{
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p)
{
if (p->isWhtL)
return true;
}
return false;
}
bool isWhite(char *mac)
{
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p)
{
if ((strcmp(p->macAdr, mac) == 0))
{
return p->isWhtL;
}
}
return false;
}
bool isBlack(char *mac)
{
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p)
{
if ((strcmp(p->macAdr, mac) == 0))
{
return p->isBlkL;
}
}
return false;
}
bool isDiscovered(char *mac)
{
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p)
{
if ((strcmp(p->macAdr, mac) == 0))
{
return p->isDisc;
}
}
return false;
}
void dumpDevices()
{
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p)
{
trc(p->macAdr);
trc(p->isDisc);
trc(p->isWhtL);
trc(p->isBlkL);
}
}
void strupp(char *beg)
{
while (*beg = toupper(*beg))
++beg;
}
#ifdef ZmqttDiscovery
void MiFloraDiscovery(char *mac)
{
#define MiFloraparametersCount 4
trc(F("MiFloraDiscovery"));
char *MiFlorasensor[MiFloraparametersCount][8] = {
{"sensor", "MiFlora-lux", mac, "illuminance", "{{ value_json.lux | is_defined }}", "", "", "lu"},
{"sensor", "MiFlora-tem", mac, "temperature", "{{ value_json.tem | is_defined }}", "", "", "°C"},
{"sensor", "MiFlora-fer", mac, "", "{{ value_json.fer | is_defined }}", "", "", "µS/cm"},
{"sensor", "MiFlora-moi", mac, "", "{{ value_json.moi | is_defined }}", "", "", "%"}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < MiFloraparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(MiFlorasensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + MiFlorasensor[i][1];
createDiscovery(MiFlorasensor[i][0],
(char *)discovery_topic.c_str(), MiFlorasensor[i][1], (char *)unique_id.c_str(),
will_Topic, MiFlorasensor[i][3], MiFlorasensor[i][4],
MiFlorasensor[i][5], MiFlorasensor[i][6], MiFlorasensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void VegTrugDiscovery(char *mac)
{
#define VegTrugparametersCount 4
trc(F("VegTrugDiscovery"));
char *VegTrugsensor[VegTrugparametersCount][8] = {
{"sensor", "VegTrug-lux", mac, "illuminance", "{{ value_json.lux | is_defined }}", "", "", "lu"},
{"sensor", "VegTrug-tem", mac, "temperature", "{{ value_json.tem | is_defined }}", "", "", "°C"},
{"sensor", "VegTrug-fer", mac, "", "{{ value_json.fer | is_defined }}", "", "", "µS/cm"},
{"sensor", "VegTrug-moi", mac, "", "{{ value_json.moi | is_defined }}", "", "", "%"}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < VegTrugparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(VegTrugsensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + VegTrugsensor[i][1];
createDiscovery(VegTrugsensor[i][0],
(char *)discovery_topic.c_str(), VegTrugsensor[i][1], (char *)unique_id.c_str(),
will_Topic, VegTrugsensor[i][3], VegTrugsensor[i][4],
VegTrugsensor[i][5], VegTrugsensor[i][6], VegTrugsensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void MiJiaDiscovery(char *mac)
{
#define MiJiaparametersCount 3
trc(F("MiJiaDiscovery"));
char *MiJiasensor[MiJiaparametersCount][8] = {
{"sensor", "MiJia-batt", mac, "battery", "{{ value_json.batt | is_defined }}", "", "", "%"},
{"sensor", "MiJia-tem", mac, "temperature", "{{ value_json.tem | is_defined }}", "", "", "°C"},
{"sensor", "MiJia-hum", mac, "humidity", "{{ value_json.hum | is_defined }}", "", "", "%"}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < MiJiaparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(MiJiasensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + MiJiasensor[i][1];
createDiscovery(MiJiasensor[i][0],
(char *)discovery_topic.c_str(), MiJiasensor[i][1], (char *)unique_id.c_str(),
will_Topic, MiJiasensor[i][3], MiJiasensor[i][4],
MiJiasensor[i][5], MiJiasensor[i][6], MiJiasensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void LYWSD02Discovery(char *mac)
{
#define LYWSD02parametersCount 3
trc(F("LYWSD02Discovery"));
char *LYWSD02sensor[LYWSD02parametersCount][8] = {
{"sensor", "LYWSD02-batt", mac, "battery", "{{ value_json.batt | is_defined }}", "", "", "V"},
{"sensor", "LYWSD02-tem", mac, "temperature", "{{ value_json.tem | is_defined }}", "", "", "°C"},
{"sensor", "LYWSD02-hum", mac, "humidity", "{{ value_json.hum | is_defined }}", "", "", "%"}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < LYWSD02parametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(LYWSD02sensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + LYWSD02sensor[i][1];
createDiscovery(LYWSD02sensor[i][0],
(char *)discovery_topic.c_str(), LYWSD02sensor[i][1], (char *)unique_id.c_str(),
will_Topic, LYWSD02sensor[i][3], LYWSD02sensor[i][4],
LYWSD02sensor[i][5], LYWSD02sensor[i][6], LYWSD02sensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void CLEARGRASSTRHDiscovery(char *mac)
{
#define CLEARGRASSTRHparametersCount 3
trc(F("CLEARGRASSTRHDiscovery"));
char *CLEARGRASSTRHsensor[CLEARGRASSTRHparametersCount][8] = {
{"sensor", "CLEARGRASSTRH-batt", mac, "battery", "{{ value_json.batt | is_defined }}", "", "", "V"},
{"sensor", "CLEARGRASSTRH-tem", mac, "temperature", "{{ value_json.tem | is_defined }}", "", "", "°C"},
{"sensor", "CLEARGRASSTRH-hum", mac, "humidity", "{{ value_json.hum | is_defined }}", "", "", "%"}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < CLEARGRASSTRHparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(CLEARGRASSTRHsensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + CLEARGRASSTRHsensor[i][1];
createDiscovery(CLEARGRASSTRHsensor[i][0],
(char *)discovery_topic.c_str(), CLEARGRASSTRHsensor[i][1], (char *)unique_id.c_str(),
will_Topic, CLEARGRASSTRHsensor[i][3], CLEARGRASSTRHsensor[i][4],
CLEARGRASSTRHsensor[i][5], CLEARGRASSTRHsensor[i][6], CLEARGRASSTRHsensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void CLEARGRASSTRHKPADiscovery(char *mac)
{
#define CLEARGRASSTRHKPAparametersCount 3
trc(F("CLEARGRASSTRHKPADiscovery"));
char *CLEARGRASSTRHKPAsensor[CLEARGRASSTRHKPAparametersCount][8] = {
{"sensor", "CLEARGRASSTRHKPA-pres", mac, "pressure", "{{ value_json.pres | is_defined }}", "", "", "kPa"},
{"sensor", "CLEARGRASSTRHKPA-tem", mac, "temperature", "{{ value_json.tem | is_defined }}", "", "", "°C"},
{"sensor", "CLEARGRASSTRHKPA-hum", mac, "humidity", "{{ value_json.hum | is_defined }}", "", "", "%"}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < CLEARGRASSTRHKPAparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(CLEARGRASSTRHKPAsensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + CLEARGRASSTRHKPAsensor[i][1];
createDiscovery(CLEARGRASSTRHKPAsensor[i][0],
(char *)discovery_topic.c_str(), CLEARGRASSTRHKPAsensor[i][1], (char *)unique_id.c_str(),
will_Topic, CLEARGRASSTRHKPAsensor[i][3], CLEARGRASSTRHKPAsensor[i][4],
CLEARGRASSTRHKPAsensor[i][5], CLEARGRASSTRHKPAsensor[i][6], CLEARGRASSTRHKPAsensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void MiScaleDiscovery(char *mac)
{
#define MiScaleparametersCount 1
trc(F("MiScaleDiscovery"));
char *MiScalesensor[MiScaleparametersCount][8] = {
{"sensor", "MiScale-weight", mac, "weight", "{{ value_json.weight | is_defined }}", "", "", "kg"},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < MiScaleparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(MiScalesensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + MiScalesensor[i][1];
createDiscovery(MiScalesensor[i][0],
(char *)discovery_topic.c_str(), MiScalesensor[i][1], (char *)unique_id.c_str(),
will_Topic, MiScalesensor[i][3], MiScalesensor[i][4],
MiScalesensor[i][5], MiScalesensor[i][6], MiScalesensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void MiLampDiscovery(char *mac)
{
#define MiLampparametersCount 1
trc(F("MiLampDiscovery"));
char *MiLampsensor[MiLampparametersCount][8] = {
{"sensor", "MiLamp-presence", mac, "presence", "{{ value_json.presence}}", "", "", "d"},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < MiLampparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(MiLampsensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + MiLampsensor[i][1];
createDiscovery(MiLampsensor[i][0],
(char *)discovery_topic.c_str(), MiLampsensor[i][1], (char *)unique_id.c_str(),
will_Topic, MiLampsensor[i][3], MiLampsensor[i][4],
MiLampsensor[i][5], MiLampsensor[i][6], MiLampsensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
void MiBandDiscovery(char *mac)
{
#define MiBandparametersCount 1
trc(F("MiBandDiscovery"));
char *MiBandsensor[MiBandparametersCount][8] = {
{"sensor", "MiBand-steps", mac, "steps", "{{ value_json.steps | is_defined }}", "", "", "nb"},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
for (int i = 0; i < MiBandparametersCount; i++)
{
trc(F("CreateDiscoverySensor"));
trc(MiBandsensor[i][1]);
String discovery_topic = String(subjectBTtoMQTT) + "/" + String(mac);
String unique_id = String(mac) + "-" + MiBandsensor[i][1];
createDiscovery(MiBandsensor[i][0],
(char *)discovery_topic.c_str(), MiBandsensor[i][1], (char *)unique_id.c_str(),
will_Topic, MiBandsensor[i][3], MiBandsensor[i][4],
MiBandsensor[i][5], MiBandsensor[i][6], MiBandsensor[i][7],
0, "", "", false, "");
}
BLEdevice device;
strcpy(device.macAdr, mac);
device.isDisc = true;
device.isWhtL = false;
device.isBlkL = false;
devices.push_back(device);
}
#endif
#ifdef ESP32
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
Ported to Arduino ESP32 by Evandro Copercini
*/
// core task implementation thanks to https://techtutorialsx.com/2017/05/09/esp32-running-code-on-a-specific-core/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
//core on which the BLE detection task will run
static int taskCore = 0;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
{
void onResult(BLEAdvertisedDevice advertisedDevice)
{
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
String mac_adress = advertisedDevice.getAddress().toString().c_str();
BLEdata.set("id", (char *)mac_adress.c_str());
mac_adress.replace(":", "");
mac_adress.toUpperCase();
String mactopic = subjectBTtoMQTT + String("/") + mac_adress;
char mac[mac_adress.length() + 1];
mac_adress.toCharArray(mac, mac_adress.length() + 1);
trc("device detected");
trc(mac);
if ((!oneWhite() || isWhite(mac)) && !isBlack(mac))
{ //if not black listed mac we go AND if we have no white mac or this mac is white we go out
if (advertisedDevice.haveName())
BLEdata.set("name", (char *)advertisedDevice.getName().c_str());
if (advertisedDevice.haveManufacturerData())
BLEdata.set("manufacturerdata", (char *)advertisedDevice.getManufacturerData().c_str());
if (advertisedDevice.haveRSSI())
BLEdata.set("rssi", (int)advertisedDevice.getRSSI());
if (advertisedDevice.haveTXPower())
BLEdata.set("txpower", (int8_t)advertisedDevice.getTXPower());
#ifdef subjectHomePresence
if (advertisedDevice.haveRSSI())
haRoomPresence(BLEdata); // this device has an rssi in consequence we can use it for home assistant room presence component
#endif
if (advertisedDevice.haveServiceData())
{
trc(F("Get services data :"));
int serviceDataCount = advertisedDevice.getServiceDataCount();
trc(serviceDataCount);
for (int j = 0; j < serviceDataCount; j++)
{
std::string serviceData = advertisedDevice.getServiceData(j);
int serviceDataLength = serviceData.length();
String returnedString = "";
for (int i = 0; i < serviceDataLength; i++)
{
int a = serviceData[i];
if (a < 16)
{
returnedString = returnedString + "0";
}
returnedString = returnedString + String(a, HEX);
}
char service_data[returnedString.length() + 1];
returnedString.toCharArray(service_data, returnedString.length() + 1);
service_data[returnedString.length()] = '\0';
#ifdef pubBLEServiceData
BLEdata.set("servicedata", service_data);
#endif
BLEdata.set("servicedatauuid", (char *)advertisedDevice.getServiceDataUUID(j).toString().c_str());
if (abs((int)BLEdata["rssi"] | 0) < abs(Minrssi))
{ // publish only the devices close enough
pub((char *)mactopic.c_str(), BLEdata);
if (strstr(BLEdata["servicedatauuid"].as<char *>(), "fe95") != NULL)
{ //Mi FLora, Mi jia, Cleargrass Method 1, LYWDS02, VegTrug
trc("Processing BLE device data");
int pos = -1;
pos = strpos(service_data, "209800");
if (pos != -1)
{
trc(F("mi flora data reading"));
//example "servicedata":"71209800bc63b6658d7cc40d0910023200"
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
MiFloraDiscovery(mac);
#endif
process_sensors(pos - 24, service_data, mac);
}
pos = -1;
pos = strpos(service_data, "20bc03");
if (pos != -1)
{
trc(F("vegtrug data reading"));
//example "servicedata":"7120bc0399c309688d7cc40d0910020000"
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
VegTrugDiscovery(mac);
#endif
process_sensors(pos - 24, service_data, mac);
}
pos = -1;
pos = strpos(service_data, "20aa01");
if (pos != -1)
{
trc(F("mi jia data reading"));
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
MiJiaDiscovery(mac);
#endif
process_sensors(pos - 26, service_data, mac);
}
pos = -1;
pos = strpos(service_data, "205b04");
if (pos != -1)
{
trc(F("LYWSD02 data reading"));
//example "servicedata":"70205b04b96ab883c8593f09041002e000"
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
LYWSD02Discovery(mac);
#endif
process_sensors(pos - 24, service_data, mac);
}
pos = -1;
pos = strpos(service_data, "304703");
if (pos != -1)
{
trc(F("ClearGrass T RH data reading method 1"));
//example "servicedata":"5030470340743e10342d58041002d6000a100164"
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
CLEARGRASSTRHDiscovery(mac);
#endif
process_sensors(pos - 26, service_data, mac);
}
pos = -1;
pos = strpos(service_data, "4030dd");
if (pos != -1)
{
trc(F("Mi Lamp data reading"));
//example "servicedata":4030DD031D0300010100
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
MiLampDiscovery(mac);
#endif
process_milamp(service_data, mac);
}
}
if (strstr(BLEdata["servicedatauuid"].as<char *>(), "181d") != NULL)
{ // Mi Scale V1
trc(F("Mi Scale V1 data reading"));
//example "servicedata":"a2ac2be307060207122b" /"a28039e3070602070e28"
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
MiScaleDiscovery(mac);
#endif
process_scale_v1(service_data, mac);
}
if (strstr(BLEdata["servicedatauuid"].as<char *>(), "181b") != NULL)
{ // Mi Scale V2
trc(F("Mi Scale V2 data reading"));
//example "servicedata":02c4e1070b1e13050c00002607 / 02a6e20705150a251df401443e /02a6e20705180c0d04d701943e
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
MiScaleDiscovery(mac);
#endif
process_scale_v2(service_data, mac);
}
if (strstr(BLEdata["servicedatauuid"].as<char *>(), "fee0") != NULL)
{ // Mi Band //0000fee0-0000-1000-8000-00805f9b34fb // ESP32 only
trc(F("Mi Band data reading"));
//example "servicedata":a21e0000
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
MiBandDiscovery(mac);
#endif
process_miband(service_data, mac);
}
if (strstr(BLEdata["servicedata"].as<char *>(), "08094c") != NULL)
{ // Clear grass with air pressure//08094c0140342d580104d8000c020702612702015a
trc(F("Clear grass data with air pressure reading"));
//example "servicedata":08094c0140342d580104 c400 2402 0702 5d27 02015a
#ifdef ZmqttDiscovery
if (!isDiscovered(mac))
CLEARGRASSTRHKPADiscovery(mac);
#endif
process_cleargrass_air(service_data, mac);
}
if (strstr(BLEdata["servicedata"].as<char *>(), "080774") != NULL)
{ // Clear grass standard method 2/0807743e10342d580104c3002c0202012a
trc(F("Clear grass data reading method 2"));
//example "servicedata":0807743e10342d580104 c300 2c02 02012a
// no discovery as it is already available with method 1
process_cleargrass(service_data, mac);
}
}
else
{
trc("Low rssi, device filtered");
}
}
}
else
{
if (abs((int)BLEdata["rssi"] | 0) < abs(Minrssi))
{ // publish only the devices close enough
pub((char *)mactopic.c_str(), BLEdata); // publish device even if there is no service data
}
else
{
trc("Low rssi, device filtered");
}
}
}
else
{
trc(F("Filtered mac device"));
}
}
};
void BLEscan()
{
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_feed = 1;
TIMERG0.wdt_wprotect = 0;
trc(F("Scan begin"));
BLEDevice::init("");
BLEScan *pBLEScan = BLEDevice::getScan(); //create new scan
MyAdvertisedDeviceCallbacks myCallbacks;
pBLEScan->setAdvertisedDeviceCallbacks(&myCallbacks);
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
BLEScanResults foundDevices = pBLEScan->start(Scan_duration);
trc(F("Scan end, deinit controller"));
esp_bt_controller_deinit();
}
void coreTask(void *pvParameters)
{
String taskMessage = "BT Task running on core ";
taskMessage = taskMessage + xPortGetCoreID();
while (true)
{
trc(taskMessage);
delay(BLEinterval);
if (client.state() == 0)
{
BLEscan();
}
else
{
trc("MQTT client disconnected no BLE scan");
delay(1000);
}
}
}
void setupBT()
{
BLEinterval = TimeBtw_Read;
Minrssi = MinimumRSSI;
trc(F("BLEinterval btw scans"));
trc(BLEinterval);
trc(F("Minrssi"));
trc(Minrssi);
// we setup a task with priority one to avoid conflict with other gateways
xTaskCreatePinnedToCore(
coreTask, /* Function to implement the task */
"coreTask", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
1, /* Priority of the task */
NULL, /* Task handle. */
taskCore); /* Core where the task should run */
trc(F("ZgatewayBT multicore ESP32 setup done "));
}
bool BTtoMQTT()
{ // for on demand BLE scans
BLEscan();
}
#else // arduino or ESP8266 working with HM10/11
#include <SoftwareSerial.h>
#define STRING_MSG "OK+DISC:"
#define QUESTION_MSG "AT+DISA?"
#define RESPONSE_MSG "OK+DISIS"
#define RESP_END_MSG "OK+DISCE"
#define SETUP_MSG "OK+RESET"
SoftwareSerial softserial(BT_RX, BT_TX);
String returnedString = "";
unsigned long timebt = 0;
// this struct define which parts of the hexadecimal chain we extract and what to do with these parts
struct decompose d[6] = {{"mac", 16, 12, true}, {"typ", 28, 2, false}, {"rsi", 30, 2, false}, {"rdl", 32, 2, false}, {"sty", 44, 4, true}, {"rda", 34, 60, false}};
void setupBT()
{
BLEinterval = TimeBtw_Read;
Minrssi = MinimumRSSI;
trc(F("BLEinterval btw scans"));
trc(BLEinterval);
trc(F("Minrssi"));
trc(Minrssi);
softserial.begin(9600);
softserial.print(F("AT+ROLE1"));
delay(100);
softserial.print(F("AT+IMME1"));
delay(100);
softserial.print(F("AT+RESET"));
delay(100);
#ifdef HM_BLUE_LED_STOP
softserial.print(F("AT+PIO11")); // When not connected (as in BLE mode) the LED is off. When connected the LED is solid on.
#endif
delay(100);
trc(F("ZgatewayBT HM1X setup done "));
}
bool BTtoMQTT()
{
//extract serial data from module in hexa format
while (softserial.available() > 0)
{
int a = softserial.read();
if (a < 16)
{
returnedString = returnedString + "0";
}
returnedString = returnedString + String(a, HEX);
}
if (millis() > (timebt + BLEinterval))
{ //retriving data
timebt = millis();
#if defined(ESP8266)
yield();
#endif
if (returnedString != "")
{
size_t pos = 0;
while ((pos = returnedString.lastIndexOf(BLEdelimiter)) != -1)
{
#if defined(ESP8266)
yield();
#endif
String token = returnedString.substring(pos);
returnedString.remove(pos, returnedString.length());
char token_char[token.length() + 1];
token.toCharArray(token_char, token.length() + 1);
trc(token);
if (token.length() > 60)
{ // we extract data only if we have detailled infos
for (int i = 0; i < 6; i++)
{
extract_char(token_char, d[i].extract, d[i].start, d[i].len, d[i].reverse, false);
if (i == 3)
d[5].len = (int)strtol(d[i].extract, NULL, 16) * 2; // extracting the length of the rest data
}
if ((strlen(d[0].extract)) == 12) // if a mac adress is detected we publish it
{
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
strupp(d[0].extract);
if (isBlack(d[0].extract))
return false; //if black listed mac we go out
if (oneWhite() && !isWhite(d[0].extract))
return false; //if we have at least one white mac and this mac is not white we go out
#ifdef subjectHomePresence
String HomePresenceId;
for (int i = 0; i < 12; i++)
{
HomePresenceId += String(d[0].extract[i]);
if (((i - 1) % 2 == 0) && (i != 11))
HomePresenceId += ":";
}
trc(F("HomePresenceId"));
trc(HomePresenceId);
BLEdata.set("id", (char *)HomePresenceId.c_str());
#endif
String topic = subjectBTtoMQTT + String("/") + String(d[0].extract);
int rssi = (int)strtol(d[2].extract, NULL, 16) - 256;
BLEdata.set("rssi", (int)rssi);
#ifdef subjectHomePresence
haRoomPresence(BLEdata); // this device has an rssi in consequence we can use it for home assistant room presence component
#endif
String service_data(d[5].extract);
service_data = service_data.substring(14);
#ifdef pubBLEServiceData
BLEdata.set("servicedata", (char *)service_data.c_str());
#endif
if (abs((int)BLEdata["rssi"] | 0) < abs(Minrssi))
{ // publish only the devices close enough
pub((char *)topic.c_str(), BLEdata);
int pos = -1;
pos = strpos(d[5].extract, "209800");
if (pos != -1)
{
trc("mi flora data reading");
#ifdef ZmqttDiscovery
if (!isDiscovered(d[0].extract))
MiFloraDiscovery(d[0].extract);
#endif
bool result = process_sensors(pos - 38, (char *)service_data.c_str(), d[0].extract);
}
pos = -1;
pos = strpos(d[5].extract, "20aa01");
//example "servicedata":"5020aa0194dfaa33342d580d1004e3002c02"
if (pos != -1)
{
trc("mi jia data reading");
#ifdef ZmqttDiscovery
if (!isDiscovered(d[0].extract))
MiJiaDiscovery(d[0].extract);
#endif
bool result = process_sensors(pos - 40, (char *)service_data.c_str(), d[0].extract);
}
pos = -1;
pos = strpos(d[5].extract, "205b04");
//example "servicedata":"141695fe70205b0461298882c8593f09061002d002"
if (pos != -1)
{
trc("LYWSD02 data reading");
#ifdef ZmqttDiscovery
if (!isDiscovered(d[0].extract))
LYWSD02Discovery(d[0].extract);
#endif
bool result = process_sensors(pos - 38, (char *)service_data.c_str(), d[0].extract);
}
pos = -1;
pos = strpos(d[5].extract, "304703");
if (pos != -1)
{
trc("CLEARGRASSTRH data reading method 1");
#ifdef ZmqttDiscovery
if (!isDiscovered(d[0].extract))
CLEARGRASSTRHDiscovery(d[0].extract);
#endif
bool result = process_sensors(pos - 40, (char *)service_data.c_str(), d[0].extract);
}
pos = -1;
pos = strpos(d[5].extract, "e30706");
if (pos != -1)
{
trc("Mi Scale data reading");
//example "servicedata":"a2ac2be307060207122b" /"a28039e3070602070e28"
#ifdef ZmqttDiscovery
if (!isDiscovered(d[0].extract))
MiScaleDiscovery(d[0].extract);
#endif
bool result = process_scale_v1((char *)service_data.c_str(), d[0].extract);
}
pos = -1;
pos = strpos(d[5].extract, "4030dd");
if (pos != -1)
{
trc(F("Mi Lamp data reading"));
//example "servicedata":4030dd31d0300010100
#ifdef ZmqttDiscovery
if (!isDiscovered(d[0].extract))
MiLampDiscovery(d[0].extract);
#endif
process_milamp((char *)service_data.c_str(), d[0].extract);
}
pos = -1;
pos = strpos(d[5].extract, "08094c"); // Clear grass with air pressure//08094c0140342d580104d8000c020702612702015a
if (pos != -1)
{
trc(F("Clear grass data with air pressure reading"));
//example "servicedata":08094c0140342d580104 c400 2402 0702 5d27 02015a
#ifdef ZmqttDiscovery
if (!isDiscovered(d[0].extract))
CLEARGRASSTRHKPADiscovery(d[0].extract);
#endif
process_cleargrass_air((char *)service_data.c_str(), d[0].extract);
}
pos = -1;
pos = strpos(d[5].extract, "080774"); // Clear grass standard method 2/0807743e10342d580104c3002c0202012a
if (pos != -1)
{
trc(F("Clear grass data reading method 2"));
//example "servicedata":0807743e10342d580104 c300 2c02 02012a
// no discovery as it is already available with method 1
process_cleargrass((char *)service_data.c_str(), d[0].extract);
}
return true;
}
else
{
trc("Low rssi, device filtered");
}
}
}
}
returnedString = ""; //init data string
return false;
}
softserial.print(F(QUESTION_MSG));
return false;
}
else
{
return false;
}
}
#endif
double value_from_service_data(char *service_data, int offset, int data_length)
{
char rev_data[data_length + 1];
char data[data_length + 1];
memcpy(rev_data, &service_data[offset], data_length);
rev_data[data_length] = '\0';
// reverse data order
revert_hex_data(rev_data, data, data_length + 1);
double value = strtol(data, NULL, 16);
if (value > 65000 && data_length <= 4)
value = value - 65535;
trc(value);
return value;
}
bool process_sensors(int offset, char *rest_data, char *mac_adress)
{
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
trc("rest_data");
trc(rest_data);
int data_length = 0;
trc("data_length");
trc(rest_data[51 + offset]);
switch (rest_data[51 + offset])
{
case '1':
case '2':
case '3':
case '4':
data_length = ((rest_data[51 + offset] - '0') * 2);
trc("valid data_length");
break;
default:
trc("invalid data_length");
return false;
}
double value = 9999;
value = value_from_service_data(rest_data, 52 + offset, data_length);
// Mi flora provides tem(perature), (earth) moi(sture), fer(tility) and lux (illuminance)
// Mi Jia provides tem(perature), batt(erry) and hum(idity)
// following the value of digit 47 we determine the type of data we get from the sensor
switch (rest_data[47 + offset])
{
case '9':
BLEdata.set("fer", (double)value);
break;
case '4':
BLEdata.set("tem", (double)value / 10);
break;
case '6':
BLEdata.set("hum", (double)value / 10);
break;
case '7':
BLEdata.set("lux", (double)value);
break;
case '8':
BLEdata.set("moi", (double)value);
break;
case 'a':
BLEdata.set("batt", (double)value);
break;
case 'd':
// humidity
value = value_from_service_data(rest_data, 52 + offset, 4);
BLEdata.set("tem", (double)value / 10);
// temperature
value = value_from_service_data(rest_data, 56 + offset, 4);
BLEdata.set("hum", (double)value / 10);
break;
default:
trc("can't read values");
return false;
}
String mactopic(mac_adress);
mactopic = subjectBTtoMQTT + String("/") + mactopic;
pub((char *)mactopic.c_str(), BLEdata);
return true;
}
bool process_scale_v1(char *rest_data, char *mac_adress)
{
//example "servicedata":"a2ac2be307060207122b" /"a28039e3070602070e28"
trc("rest_data");
trc(rest_data);
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
double weight = value_from_service_data(rest_data, 2, 4) / 200;
//Set Json value
BLEdata.set("weight", (double)weight);
// Publish weight
String mactopic(mac_adress);
mactopic = subjectBTtoMQTT + String("/") + mactopic;
pub((char *)mactopic.c_str(), BLEdata);
return true;
}
bool process_scale_v2(char *rest_data, char *mac_adress)
{
//example "servicedata":"a2ac2be307060207122b" /"a28039e3070602070e28"
trc("rest_data");
trc(rest_data);
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
double weight = value_from_service_data(rest_data, 22, 4) / 200;
double impedance = value_from_service_data(rest_data, 18, 4);
//Set Json values
BLEdata.set("weight", (double)weight);
BLEdata.set("impedance", (double)impedance);
// Publish weight
String mactopic(mac_adress);
mactopic = subjectBTtoMQTT + String("/") + mactopic;
pub((char *)mactopic.c_str(), BLEdata);
return true;
}
bool process_miband(char *rest_data, char *mac_adress)
{
//example "servicedata":"a21e0000"
trc("rest_data");
trc(rest_data);
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
double steps = value_from_service_data(rest_data, 0, 4);
//Set Json value
BLEdata.set("steps", (double)steps);
// Publish weight
String mactopic(mac_adress);
mactopic = subjectBTtoMQTT + String("/") + mactopic;
pub((char *)mactopic.c_str(), BLEdata);
return true;
}
bool process_milamp(char *rest_data, char *mac_adress)
{
//example "servicedata":"4030dd31d0300010100"
trc("rest_data");
trc(rest_data);
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
long darkness = value_from_service_data(rest_data, 8, 2);
//Set Json value
BLEdata.set("presence", (bool)"true");
BLEdata.set("darkness", (long)darkness);
// Publish
String mactopic(mac_adress);
mactopic = subjectBTtoMQTT + String("/") + mactopic;
pub((char *)mactopic.c_str(), BLEdata);
return true;
}
bool process_cleargrass_air(char *rest_data, char *mac_adress)
{
//example "servicedata":"08094c0140342d580104c400240207025d2702015a"
//decoding "08094c0140342d580104 temp:c400 hum:2402 0702 pressure:5d27 02015a"
trc("rest_data");
trc(rest_data);
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
double value = 9999;
// humidity
value = value_from_service_data(rest_data, 20, 4);
BLEdata.set("tem", (double)value / 10);
// temperature
value = value_from_service_data(rest_data, 24, 4);
BLEdata.set("hum", (double)value / 10);
// air pressure
value = value_from_service_data(rest_data, 32, 4);
BLEdata.set("pres", (double)value / 100);
String mactopic(mac_adress);
mactopic = subjectBTtoMQTT + String("/") + mactopic;
pub((char *)mactopic.c_str(), BLEdata);
return true;
}
bool process_cleargrass(char *rest_data, char *mac_adress)
{
//example "servicedata":0807743e10342d580104 c300 2c02 02012a
trc("rest_data");
trc(rest_data);
trc(F("Creating BLE buffer"));
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
JsonObject &BLEdata = jsonBuffer.createObject();
double value = 9999;
// humidity
value = value_from_service_data(rest_data, 20, 4);
BLEdata.set("tem", (double)value / 10);
// temperature
value = value_from_service_data(rest_data, 24, 4);
BLEdata.set("hum", (double)value / 10);
String mactopic(mac_adress);
mactopic = subjectBTtoMQTT + String("/") + mactopic;
pub((char *)mactopic.c_str(), BLEdata);
return true;
}
#ifdef subjectHomePresence
void haRoomPresence(JsonObject &HomePresence)
{
int BLErssi = HomePresence["rssi"];
trc(F("BLErssi"));
trc(BLErssi);
int txPower = HomePresence["txpower"] | 0;
if (txPower >= 0)
txPower = -59; //if tx power is not found we set a default calibration value
trc(F("txPower"));
trc(txPower);
double ratio = BLErssi * 1.0 / txPower;
double distance;
if (ratio < 1.0)
{
distance = pow(ratio, 10);
}
else
{
distance = (0.89976) * pow(ratio, 7.7095) + 0.111;
}
HomePresence["distance"] = distance;
trc(F("BLE DISTANCE :"));
trc(distance);
String topic = String(Base_Topic) + "home_presence/" + String(gateway_name);
pub_custom_topic((char *)topic.c_str(), HomePresence, false);
}
#endif
void MQTTtoBT(char *topicOri, JsonObject &BTdata)
{ // json object decoding
if (cmpToMainTopic(topicOri, subjectMQTTtoBTset))
{
trc(F("MQTTtoBT json set"));
// Black list & white list set
int WLsize = BTdata["white-list"].size();
if (WLsize > 0)
{
trc(F("WL set"));
for (int i = 0; i < WLsize; i++)
{
const char *whiteMac = BTdata["white-list"][i];
setWorBMac((char *)whiteMac, true);
}
}
int BLsize = BTdata["black-list"].size();
if (BLsize > 0)
{
trc(F("BL set"));
for (int i = 0; i < BLsize; i++)
{
const char *blackMac = BTdata["black-list"][i];
setWorBMac((char *)blackMac, false);
}
}
if (BLsize > 0 || WLsize > 0)
dumpDevices();
// Scan interval set
if (BTdata.containsKey("interval"))
{
trc(F("BLE interval"));
// storing BLE interval for further use if needed
unsigned int prevBLEinterval = BLEinterval;
trc("previous interval");
trc(BLEinterval);
// set BLE interval if present if not setting default value
BLEinterval = (unsigned int)BTdata["interval"];
trc("new interval");
trc(BLEinterval);
if (BLEinterval == 0)
{
if (BTtoMQTT()) // as BLEinterval is = to 0 we can launch the loop and the scan will execute immediately
trc(F("Scan done"));
BLEinterval = prevBLEinterval; // as 0 was just used as a command we recover previous scan duration
}
}
// MinRSSI set
if (BTdata.containsKey("minrssi"))
{
trc(F("Min RSSI"));
// storing Min RSSI for further use if needed
trc("previous Minrssi");
trc(Minrssi);
// set Min RSSI if present if not setting default value
Minrssi = (unsigned int)BTdata["minrssi"];
trc("new Minrssi");
trc(Minrssi);
}
}
}
#endif