* Add a lock mechanism to improve the OTA chances of success change TimeBeforeMQTTconnect to InitialMQTTConnectionTimeout move delay(BLEinterval); after the scan so the first scan will always be done at start now remove non relevant delay add stopProcessing and startProcessing functions
975 lines
35 KiB
Arduino
975 lines
35 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
|
|
|
|
# ifdef ESP32
|
|
# include "FreeRTOS.h"
|
|
FreeRTOS::Semaphore semaphoreCreateOrUpdateDevice = FreeRTOS::Semaphore("createOrUpdateDevice");
|
|
// Headers used for deep sleep functions
|
|
# include <driver/adc.h>
|
|
# include <esp_bt.h>
|
|
# include <esp_bt_main.h>
|
|
# include <esp_wifi.h>
|
|
# endif
|
|
|
|
# include <vector>
|
|
using namespace std;
|
|
vector<BLEdevice> devices;
|
|
|
|
static BLEdevice NO_DEVICE_FOUND = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false, false, false};
|
|
static bool oneWhite = false;
|
|
BLEdevice* getDeviceByMac(const char* mac);
|
|
|
|
BLEdevice* getDeviceByMac(const char* mac) {
|
|
Log.trace(F("getDeviceByMac %s" CR), mac);
|
|
|
|
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p) {
|
|
if ((strcmp(p->macAdr, mac) == 0)) {
|
|
return &(*p);
|
|
}
|
|
}
|
|
return &NO_DEVICE_FOUND;
|
|
}
|
|
|
|
bool updateWorB(JsonObject& BTdata, bool isWhite) {
|
|
Log.trace(F("update WorB" CR));
|
|
const char* jsonKey = isWhite ? "white-list" : "black-list";
|
|
|
|
int size = BTdata[jsonKey].size();
|
|
if (size == 0)
|
|
return false;
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
const char* mac = BTdata[jsonKey][i];
|
|
|
|
createOrUpdateDevice(mac, (isWhite ? device_flags_isWhiteL : device_flags_isBlackL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void createOrUpdateDevice(const char* mac, uint8_t flags) {
|
|
# ifdef ESP32
|
|
if (!semaphoreCreateOrUpdateDevice.take(30000, "createOrUpdateDevice"))
|
|
return;
|
|
# endif
|
|
|
|
BLEdevice* device = getDeviceByMac(mac);
|
|
if (device == &NO_DEVICE_FOUND) {
|
|
Log.trace(F("add %s" CR), mac);
|
|
//new device
|
|
device = new BLEdevice();
|
|
strcpy(device->macAdr, mac);
|
|
device->isDisc = flags & device_flags_isDisc;
|
|
device->isWhtL = flags & device_flags_isWhiteL;
|
|
device->isBlkL = flags & device_flags_isBlackL;
|
|
devices.push_back(*device);
|
|
} else {
|
|
Log.trace(F("update %s" CR), mac);
|
|
|
|
if (flags & device_flags_isDisc) {
|
|
device->isDisc = true;
|
|
}
|
|
|
|
if (flags & device_flags_isWhiteL || flags & device_flags_isBlackL) {
|
|
device->isWhtL = flags & device_flags_isWhiteL;
|
|
device->isBlkL = flags & device_flags_isBlackL;
|
|
}
|
|
}
|
|
|
|
// update oneWhite flag
|
|
oneWhite = oneWhite || device->isWhtL;
|
|
|
|
# ifdef ESP32
|
|
semaphoreCreateOrUpdateDevice.give();
|
|
# endif
|
|
}
|
|
|
|
# define isWhite(device) device->isWhtL
|
|
# define isBlack(device) device->isBlkL
|
|
# define isDiscovered(device) device->isDisc
|
|
|
|
void dumpDevices() {
|
|
for (vector<BLEdevice>::iterator p = devices.begin(); p != devices.end(); ++p) {
|
|
Log.trace(F("macAdr %s" CR), p->macAdr);
|
|
Log.trace(F("isDisc %d" CR), p->isDisc);
|
|
Log.trace(F("isWhtL %d" CR), p->isWhtL);
|
|
Log.trace(F("isBlkL %d" CR), p->isBlkL);
|
|
}
|
|
}
|
|
|
|
void strupp(char* beg) {
|
|
while (*beg = toupper(*beg))
|
|
++beg;
|
|
}
|
|
|
|
# ifdef ZmqttDiscovery
|
|
void MiFloraDiscovery(char* mac) {
|
|
# define MiFloraparametersCount 4
|
|
Log.trace(F("MiFloraDiscovery" CR));
|
|
char* MiFlorasensor[MiFloraparametersCount][8] = {
|
|
{"sensor", "MiFlora-lux", mac, "illuminance", jsonLux, "", "", "lx"},
|
|
{"sensor", "MiFlora-tem", mac, "temperature", jsonTemp, "", "", "°C"},
|
|
{"sensor", "MiFlora-fer", mac, "", jsonFer, "", "", "µS/cm"},
|
|
{"sensor", "MiFlora-moi", mac, "", jsonMoi, "", "", "%"}
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, MiFlorasensor, MiFloraparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void VegTrugDiscovery(char* mac) {
|
|
# define VegTrugparametersCount 4
|
|
Log.trace(F("VegTrugDiscovery" CR));
|
|
char* VegTrugsensor[VegTrugparametersCount][8] = {
|
|
{"sensor", "VegTrug-lux", mac, "illuminance", jsonLux, "", "", "lx"},
|
|
{"sensor", "VegTrug-tem", mac, "temperature", jsonTemp, "", "", "°C"},
|
|
{"sensor", "VegTrug-fer", mac, "", jsonFer, "", "", "µS/cm"},
|
|
{"sensor", "VegTrug-moi", mac, "", jsonMoi, "", "", "%"}
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, VegTrugsensor, VegTrugparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void MiJiaDiscovery(char* mac) {
|
|
# define MiJiaparametersCount 3
|
|
Log.trace(F("MiJiaDiscovery" CR));
|
|
char* MiJiasensor[MiJiaparametersCount][8] = {
|
|
{"sensor", "MiJia-batt", mac, "battery", jsonBatt, "", "", "%"},
|
|
{"sensor", "MiJia-tem", mac, "temperature", jsonTemp, "", "", "°C"},
|
|
{"sensor", "MiJia-hum", mac, "humidity", jsonHum, "", "", "%"}
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, MiJiasensor, MiJiaparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void LYWSD02Discovery(char* mac) {
|
|
# define LYWSD02parametersCount 3
|
|
Log.trace(F("LYWSD02Discovery" CR));
|
|
char* LYWSD02sensor[LYWSD02parametersCount][8] = {
|
|
{"sensor", "LYWSD02-batt", mac, "battery", jsonBatt, "", "", "V"},
|
|
{"sensor", "LYWSD02-tem", mac, "temperature", jsonTemp, "", "", "°C"},
|
|
{"sensor", "LYWSD02-hum", mac, "humidity", jsonHum, "", "", "%"}
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, LYWSD02sensor, LYWSD02parametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void CLEARGRASSTRHDiscovery(char* mac) {
|
|
# define CLEARGRASSTRHparametersCount 3
|
|
Log.trace(F("CLEARGRASSTRHDiscovery" CR));
|
|
char* CLEARGRASSTRHsensor[CLEARGRASSTRHparametersCount][8] = {
|
|
{"sensor", "CLEARGRASSTRH-batt", mac, "battery", jsonBatt, "", "", "V"},
|
|
{"sensor", "CLEARGRASSTRH-tem", mac, "temperature", jsonTemp, "", "", "°C"},
|
|
{"sensor", "CLEARGRASSTRH-hum", mac, "humidity", jsonHum, "", "", "%"}
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, CLEARGRASSTRHsensor, CLEARGRASSTRHparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void CLEARGRASSCGD1Discovery(char* mac) {
|
|
# define CLEARGRASSCGD1parametersCount 3
|
|
Log.trace(F("CLEARGRASSCGD1Discovery" CR));
|
|
char* CLEARGRASSCGD1sensor[CLEARGRASSCGD1parametersCount][8] = {
|
|
{"sensor", "CLEARGRASSCGD1-batt", mac, "battery", jsonBatt, "", "", "V"},
|
|
{"sensor", "CLEARGRASSCGD1-tem", mac, "temperature", jsonTemp, "", "", "°C"},
|
|
{"sensor", "CLEARGRASSCGD1-hum", mac, "humidity", jsonHum, "", "", "%"}
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, CLEARGRASSCGD1sensor, CLEARGRASSCGD1parametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void CLEARGRASSTRHKPADiscovery(char* mac) {
|
|
# define CLEARGRASSTRHKPAparametersCount 3
|
|
Log.trace(F("CLEARGRASSTRHKPADiscovery" CR));
|
|
char* CLEARGRASSTRHKPAsensor[CLEARGRASSTRHKPAparametersCount][8] = {
|
|
{"sensor", "CLEARGRASSTRHKPA-pres", mac, "pressure", jsonPres, "", "", "kPa"},
|
|
{"sensor", "CLEARGRASSTRHKPA-tem", mac, "temperature", jsonTemp, "", "", "°C"},
|
|
{"sensor", "CLEARGRASSTRHKPA-hum", mac, "humidity", jsonHum, "", "", "%"}
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, CLEARGRASSTRHKPAsensor, CLEARGRASSTRHKPAparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void MiScaleDiscovery(char* mac) {
|
|
# define MiScaleparametersCount 1
|
|
Log.trace(F("MiScaleDiscovery" CR));
|
|
char* MiScalesensor[MiScaleparametersCount][8] = {
|
|
{"sensor", "MiScale-weight", mac, "", jsonWeight, "", "", "kg"},
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, MiScalesensor, MiScaleparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void MiLampDiscovery(char* mac) {
|
|
# define MiLampparametersCount 1
|
|
Log.trace(F("MiLampDiscovery" CR));
|
|
char* MiLampsensor[MiLampparametersCount][8] = {
|
|
{"sensor", "MiLamp-presence", mac, "", jsonPresence, "", "", "d"},
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, MiLampsensor, MiLampparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
|
|
void MiBandDiscovery(char* mac) {
|
|
# define MiBandparametersCount 1
|
|
Log.trace(F("MiBandDiscovery" CR));
|
|
char* MiBandsensor[MiBandparametersCount][8] = {
|
|
{"sensor", "MiBand-steps", mac, "", jsonStep, "", "", "nb"},
|
|
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
|
|
};
|
|
|
|
createDiscoveryFromList(mac, MiBandsensor, MiBandparametersCount);
|
|
createOrUpdateDevice(mac, device_flags_isDisc);
|
|
}
|
|
# else
|
|
void MiFloraDiscovery(char* mac) {}
|
|
void VegTrugDiscovery(char* mac) {}
|
|
void MiJiaDiscovery(char* mac) {}
|
|
void LYWSD02Discovery(char* mac) {}
|
|
void CLEARGRASSTRHDiscovery(char* mac) {}
|
|
void CLEARGRASSCGD1Discovery(char* mac) {}
|
|
void CLEARGRASSTRHKPADiscovery(char* mac) {}
|
|
void MiScaleDiscovery(char* mac) {}
|
|
void MiLampDiscovery(char* mac) {}
|
|
void MiBandDiscovery(char* mac) {}
|
|
# 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 <BLEAdvertisedDevice.h>
|
|
# include <BLEDevice.h>
|
|
# include <BLEScan.h>
|
|
# include <BLEUtils.h>
|
|
|
|
# include "soc/timer_group_reg.h"
|
|
# include "soc/timer_group_struct.h"
|
|
|
|
//core on which the BLE detection task will run
|
|
static int taskCore = 0;
|
|
// Process lock when we want to use a critical function like OTA for example
|
|
bool ProcessLock = false;
|
|
|
|
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
|
|
void onResult(BLEAdvertisedDevice advertisedDevice) {
|
|
Log.trace(F("Creating BLE buffer" CR));
|
|
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
|
|
JsonObject& BLEdata = jsonBuffer.createObject();
|
|
String mac_adress = advertisedDevice.getAddress().toString().c_str();
|
|
mac_adress.toUpperCase();
|
|
BLEdata.set("id", (char*)mac_adress.c_str());
|
|
mac_adress.replace(":", "");
|
|
String mactopic = subjectBTtoMQTT + String("/") + mac_adress;
|
|
char mac[mac_adress.length() + 1];
|
|
mac_adress.toCharArray(mac, mac_adress.length() + 1);
|
|
Log.notice(F("Device detected: %s" CR), mac);
|
|
BLEdevice* device = getDeviceByMac(mac);
|
|
|
|
if ((!oneWhite || isWhite(device)) && !isBlack(device)) { //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 pubBLEManufacturerData
|
|
if (advertisedDevice.haveManufacturerData()) {
|
|
char* manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t*)advertisedDevice.getManufacturerData().data(), advertisedDevice.getManufacturerData().length());
|
|
Log.trace(F("Manufacturer Data: %s" CR), manufacturerdata);
|
|
BLEdata.set("manufacturerdata", manufacturerdata);
|
|
}
|
|
# endif
|
|
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()) {
|
|
int serviceDataCount = advertisedDevice.getServiceDataCount();
|
|
Log.trace(F("Get services data number: %d" CR), 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';
|
|
Log.trace(F("Service data: %s" CR), service_data);
|
|
BLEdata.set("servicedata", service_data);
|
|
std::string serviceDatauuid = advertisedDevice.getServiceDataUUID(j).toString();
|
|
Log.trace(F("Service data UUID: %s" CR), (char*)serviceDatauuid.c_str());
|
|
BLEdata.set("servicedatauuid", (char*)serviceDatauuid.c_str());
|
|
PublishDeviceData(BLEdata);
|
|
}
|
|
} else {
|
|
PublishDeviceData(BLEdata); // publish device even if there is no service data
|
|
}
|
|
} else {
|
|
Log.trace(F("Filtered mac device" CR));
|
|
}
|
|
}
|
|
};
|
|
|
|
void BLEscan() {
|
|
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
|
TIMERG0.wdt_feed = 1;
|
|
TIMERG0.wdt_wprotect = 0;
|
|
Log.notice(F("Scan begin" CR));
|
|
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);
|
|
Log.notice(F("Scan end, deinit controller" CR));
|
|
esp_bt_controller_deinit();
|
|
}
|
|
|
|
void stopProcessing() {
|
|
Log.notice(F("Stop BLE processing" CR));
|
|
ProcessLock = true;
|
|
}
|
|
|
|
void startProcessing() {
|
|
Log.notice(F("Start BLE processing" CR));
|
|
ProcessLock = false;
|
|
}
|
|
|
|
void coreTask(void* pvParameters) {
|
|
while (true) {
|
|
Log.trace(F("BT Task running on core: %d" CR), xPortGetCoreID());
|
|
if (!ProcessLock) {
|
|
int n = 0;
|
|
while (client.state() != 0 && n <= InitialMQTTConnectionTimeout) {
|
|
n++;
|
|
Log.trace(F("Wait for MQTT on core: %d attempt: %d" CR), xPortGetCoreID(), n);
|
|
delay(1000);
|
|
}
|
|
if (client.state() != 0) {
|
|
Log.warning(F("MQTT client disconnected no BLE scan" CR));
|
|
} else {
|
|
pinMode(LOW_POWER_LED, OUTPUT);
|
|
if (low_power_mode == 2)
|
|
digitalWrite(LOW_POWER_LED, 1 - LOW_POWER_LED_OFF);
|
|
BLEscan();
|
|
//only change LOW_POWER_LED if low power mode is enabled
|
|
if (low_power_mode)
|
|
digitalWrite(LOW_POWER_LED, LOW_POWER_LED_OFF);
|
|
}
|
|
if (low_power_mode) {
|
|
lowPowerESP32();
|
|
} else {
|
|
delay(BLEinterval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void lowPowerESP32() // low power mode
|
|
{
|
|
Log.trace(F("Going to deep sleep for: %l s" CR), (BLEinterval / 1000));
|
|
deepSleep(BLEinterval * 1000);
|
|
}
|
|
|
|
void deepSleep(uint64_t time_in_us) {
|
|
# if defined(ZboardM5STACK) || defined(ZboardM5STICKC)
|
|
sleepScreen();
|
|
esp_sleep_enable_ext0_wakeup((gpio_num_t)SLEEP_BUTTON, LOW);
|
|
# endif
|
|
|
|
Log.trace(F("Deactivating ESP32 components" CR));
|
|
esp_wifi_stop();
|
|
esp_bluedroid_disable();
|
|
esp_bluedroid_deinit();
|
|
esp_bt_controller_disable();
|
|
esp_bt_controller_deinit();
|
|
esp_bt_mem_release(ESP_BT_MODE_BTDM);
|
|
adc_power_off();
|
|
esp_wifi_stop();
|
|
esp_deep_sleep(time_in_us);
|
|
}
|
|
|
|
void changelow_power_mode(int newLowPowerMode) {
|
|
Log.notice(F("Changing LOW POWER mode to: %d" CR), newLowPowerMode);
|
|
# if defined(ZboardM5STACK) || defined(ZboardM5STICKC)
|
|
if (low_power_mode == 2) {
|
|
# ifdef ZboardM5STACK
|
|
M5.Lcd.wakeup();
|
|
# endif
|
|
# ifdef ZboardM5STICKC
|
|
M5.Axp.SetLDO2(true);
|
|
M5.Lcd.begin();
|
|
# endif
|
|
}
|
|
char lpm[2];
|
|
sprintf(lpm, "%d", newLowPowerMode);
|
|
M5Display("Changing LOW POWER mode to:", lpm, "");
|
|
# endif
|
|
low_power_mode = newLowPowerMode;
|
|
preferences.begin(Gateway_Short_Name, false);
|
|
preferences.putUInt("low_power_mode", low_power_mode);
|
|
preferences.end();
|
|
}
|
|
|
|
void setupBT() {
|
|
Log.notice(F("BLEinterval: %d" CR), BLEinterval);
|
|
Log.notice(F("Minrssi: %d" CR), Minrssi);
|
|
Log.notice(F("Low Power Mode: %d" CR), low_power_mode);
|
|
|
|
// 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 */
|
|
Log.trace(F("ZgatewayBT multicore ESP32 setup done " CR));
|
|
}
|
|
|
|
bool BTtoMQTT() { // for on demand BLE scans
|
|
BLEscan();
|
|
}
|
|
# else // arduino or ESP8266 working with HM10/11
|
|
|
|
# include <SoftwareSerial.h>
|
|
# define QUESTION_MSG "AT+DISA?"
|
|
|
|
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", 56, 60, false}};
|
|
|
|
void setupBT() {
|
|
BLEinterval = TimeBtw_Read;
|
|
Minrssi = MinimumRSSI;
|
|
Log.notice(F("BLEinterval: %d" CR), BLEinterval);
|
|
Log.notice(F("Minrssi: %d" CR), Minrssi);
|
|
softserial.begin(HMSerialSpeed);
|
|
softserial.print(F("AT+ROLE1" CR));
|
|
delay(100);
|
|
softserial.print(F("AT+IMME1" CR));
|
|
delay(100);
|
|
softserial.print(F("AT+RESET" CR));
|
|
delay(100);
|
|
# ifdef HM_BLUE_LED_STOP
|
|
softserial.print(F("AT+PIO11" CR)); // When not connected (as in BLE mode) the LED is off. When connected the LED is solid on.
|
|
# endif
|
|
delay(100);
|
|
Log.trace(F("ZgatewayBT HM1X setup done " CR));
|
|
}
|
|
|
|
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)) { //retrieving data
|
|
timebt = millis();
|
|
# if defined(ESP8266)
|
|
yield();
|
|
# endif
|
|
if (returnedString != "") {
|
|
Log.verbose(F("returnedString: %s" CR), (char*)returnedString.c_str());
|
|
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);
|
|
Log.trace(F("Token: %s" CR), token_char);
|
|
if (token.length() > 60) { // we extract data only if we have infos (BLEdelimiter length + 4)
|
|
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 BLE device is detected we analyse it
|
|
{
|
|
Log.trace(F("Creating BLE buffer" CR));
|
|
StaticJsonBuffer<JSON_MSG_BUFFER> jsonBuffer;
|
|
JsonObject& BLEdata = jsonBuffer.createObject();
|
|
strupp(d[0].extract);
|
|
|
|
BLEdevice* device = getDeviceByMac(d[0].extract);
|
|
|
|
if (isBlack(device))
|
|
return false; //if black listed mac we go out
|
|
if (oneWhite && !isWhite(device))
|
|
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 += ":";
|
|
}
|
|
Log.trace(F("HomePresenceId %s" CR), (char*)HomePresenceId.c_str());
|
|
BLEdata.set("id", (char*)HomePresenceId.c_str());
|
|
# endif
|
|
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
|
|
Log.trace(F("Service data: %s" CR), d[5].extract);
|
|
BLEdata.set("servicedata", d[5].extract);
|
|
PublishDeviceData(BLEdata);
|
|
}
|
|
}
|
|
}
|
|
returnedString = ""; //init data string
|
|
return false;
|
|
}
|
|
softserial.print(F(QUESTION_MSG));
|
|
return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
# endif
|
|
|
|
void RemoveJsonPropertyIf(JsonObject& obj, char* key, bool condition) {
|
|
if (condition) {
|
|
Log.trace(F("Removing %s" CR), key);
|
|
obj.remove(key);
|
|
}
|
|
}
|
|
|
|
double value_from_service_data(const 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;
|
|
Log.trace(F("value %D" CR), value);
|
|
return value;
|
|
}
|
|
|
|
boolean valid_service_data(const char* data) {
|
|
int size = strlen(data);
|
|
for (int i = 0; i < size; ++i) {
|
|
if (data[i] != 48) // 48 correspond to 0 in ASCII table
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void launch_discovery(JsonObject& BLEdata, char* mac) {
|
|
BLEdevice* device = getDeviceByMac(mac);
|
|
if (!isDiscovered(device) && BLEdata.containsKey("model")) {
|
|
Log.trace(F("Launching discovery of %s" CR), mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "HHCCJCY01HHCC") == 0) MiFloraDiscovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "VegTrug") == 0) VegTrugDiscovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "LYWSDCGQ") == 0) MiJiaDiscovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "LYWSD02") == 0) LYWSD02Discovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "CGG1") == 0) CLEARGRASSTRHDiscovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "CGP1W") == 0) CLEARGRASSTRHKPADiscovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "MUE4094RT") == 0) MiLampDiscovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "CGD1") == 0) CLEARGRASSCGD1Discovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "MiBand") == 0) MiBandDiscovery(mac);
|
|
if (strcmp(BLEdata["model"].as<const char*>(), "XMTZC04HM") == 0 ||
|
|
strcmp(BLEdata["model"].as<const char*>(), "XMTZC05HM") == 0) MiScaleDiscovery(mac);
|
|
} else {
|
|
Log.trace(F("Device already discovered or model not detected" CR));
|
|
}
|
|
}
|
|
|
|
void PublishDeviceData(JsonObject& BLEdata) {
|
|
if (abs((int)BLEdata["rssi"] | 0) < abs(Minrssi)) { // process only the devices close enough
|
|
JsonObject& BLEdataOut = process_bledata(BLEdata);
|
|
String mac_adress = BLEdataOut["id"].as<const char*>();
|
|
mac_adress.replace(":", "");
|
|
char mac[mac_adress.length() + 1];
|
|
mac_adress.toCharArray(mac, mac_adress.length() + 1);
|
|
# ifdef ZmqttDiscovery
|
|
launch_discovery(BLEdataOut, mac);
|
|
# endif
|
|
# if !pubBLEServiceUUID
|
|
RemoveJsonPropertyIf(BLEdataOut, "servicedatauuid", BLEdataOut.containsKey("servicedatauuid"));
|
|
# endif
|
|
# if !pubKnownBLEServiceData
|
|
RemoveJsonPropertyIf(BLEdataOut, "servicedata", BLEdataOut.containsKey("model") && BLEdataOut.containsKey("servicedata"));
|
|
# endif
|
|
String mactopic(mac);
|
|
mactopic = subjectBTtoMQTT + String("/") + mactopic;
|
|
pub((char*)mactopic.c_str(), BLEdataOut);
|
|
} else {
|
|
Log.trace(F("Low rssi, device filtered" CR));
|
|
}
|
|
}
|
|
|
|
JsonObject& process_bledata(JsonObject& BLEdata) {
|
|
if (BLEdata.containsKey("servicedata")) {
|
|
Log.trace(F("Checking BLE service data validity" CR));
|
|
const char* service_data = (const char*)(BLEdata["servicedata"] | "");
|
|
if (valid_service_data(service_data)) {
|
|
Log.trace(F("Searching BLE device data %s size %d" CR), service_data, strlen(service_data));
|
|
Log.trace(F("Is it a mi flora ?" CR));
|
|
if (strstr(service_data, "209800") != NULL) {
|
|
Log.trace(F("mi flora data reading" CR));
|
|
BLEdata.set("model", "HHCCJCY01HHCC");
|
|
|
|
return process_sensors(2, BLEdata);
|
|
}
|
|
Log.trace(F("Is it a vegtrug ?" CR));
|
|
if (strstr(service_data, "20bc03") != NULL && strlen(service_data) > ServicedataMinLength) {
|
|
Log.trace(F("vegtrug data reading" CR));
|
|
BLEdata.set("model", "VegTrug");
|
|
|
|
return process_sensors(2, BLEdata);
|
|
}
|
|
Log.trace(F("Is it a LYWSDCGQ?" CR));
|
|
if (strstr(service_data, "20aa01") != NULL && strlen(service_data) > ServicedataMinLength) {
|
|
Log.trace(F("LYWSDCGQ data reading" CR));
|
|
BLEdata.set("model", "LYWSDCGQ");
|
|
|
|
return process_sensors(0, BLEdata);
|
|
}
|
|
Log.trace(F("Is it a LYWSD02?" CR));
|
|
if (strstr(service_data, "205b04") != NULL && strlen(service_data) > ServicedataMinLength) {
|
|
Log.trace(F("LYWSD02 data reading" CR));
|
|
BLEdata.set("model", "LYWSD02");
|
|
|
|
return process_sensors(2, BLEdata);
|
|
}
|
|
Log.trace(F("Is it a CGG1?" CR));
|
|
if (strstr(service_data, "304703") != NULL && strlen(service_data) > ServicedataMinLength) {
|
|
Log.trace(F("CGG1 data reading method 1" CR));
|
|
BLEdata.set("model", "CGG1");
|
|
|
|
return process_sensors(0, BLEdata);
|
|
}
|
|
Log.trace(F("Is it a MUE4094RT?" CR));
|
|
if (strstr(service_data, "4030dd") != NULL) {
|
|
Log.trace(F("MUE4094RT data reading" CR));
|
|
BLEdata.set("model", "MUE4094RT");
|
|
|
|
return process_milamp(BLEdata);
|
|
}
|
|
Log.trace(F("Is it a CGP1W?" CR));
|
|
if (strstr(service_data, "08094c") != NULL && strlen(service_data) > ServicedataMinLength) {
|
|
Log.trace(F("CGP1W data reading" CR));
|
|
BLEdata.set("model", "CGP1W");
|
|
|
|
return process_cleargrass(BLEdata, true);
|
|
}
|
|
Log.trace(F("Is it a CGG1" CR));
|
|
if (strstr(service_data, "080774") != NULL) {
|
|
Log.trace(F("CGG1 method 2" CR));
|
|
BLEdata.set("model", "CGG1");
|
|
// no discovery as it is already available with method 1
|
|
return process_cleargrass(BLEdata, false);
|
|
}
|
|
Log.trace(F("Is it a CGD1?" CR));
|
|
if ((strstr(service_data, "080caf") != NULL || strstr(service_data, "080c09") != NULL) && (strlen(service_data) > ServicedataMinLength)) {
|
|
Log.trace(F("CGD1 data reading" CR));
|
|
BLEdata.set("model", "CGD1");
|
|
|
|
return process_cleargrass(BLEdata, false);
|
|
}
|
|
if (BLEdata.containsKey("servicedatauuid")) {
|
|
const char* service_datauuid = (const char*)(BLEdata["servicedatauuid"] | "");
|
|
Log.trace(F("servicedatauuid %s" CR), service_datauuid);
|
|
Log.trace(F("Is it a MiBand?" CR));
|
|
if (strstr(service_datauuid, "fee0") != NULL) {
|
|
Log.trace(F("Mi Band data reading" CR));
|
|
BLEdata.set("model", "MiBand");
|
|
|
|
return process_miband(BLEdata);
|
|
}
|
|
Log.trace(F("Is it a XMTZC04HM?" CR));
|
|
if (strstr(service_datauuid, "181d") != NULL) {
|
|
Log.trace(F("XMTZC04HM data reading" CR));
|
|
BLEdata.set("model", "XMTZC04HM");
|
|
|
|
return process_scale_v1(BLEdata);
|
|
}
|
|
Log.trace(F("Is it a XMTZC05HM?" CR));
|
|
if (strstr(service_datauuid, "181b") != NULL) {
|
|
Log.trace(F("XMTZC05HM data reading" CR));
|
|
BLEdata.set("model", "XMTZC05HM");
|
|
;
|
|
|
|
return process_scale_v2(BLEdata);
|
|
}
|
|
}
|
|
} else {
|
|
Log.trace(F("Non valid service data, removing it" CR));
|
|
BLEdata.remove("servicedata");
|
|
}
|
|
|
|
# if !pubUnknownBLEServiceData
|
|
Log.trace(F("Unknown service data, removing it" CR));
|
|
BLEdata.remove("servicedata");
|
|
# endif
|
|
}
|
|
|
|
return BLEdata;
|
|
}
|
|
|
|
JsonObject& process_sensors(int offset, JsonObject& BLEdata) {
|
|
const char* servicedata = BLEdata["servicedata"].as<const char*>();
|
|
int data_length = 0;
|
|
|
|
switch (servicedata[27 + offset]) {
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
data_length = ((servicedata[27 + offset] - '0') * 2);
|
|
Log.trace(F("Valid data_length: %d" CR), data_length);
|
|
break;
|
|
default:
|
|
Log.trace(F("Invalid data_length, not enriching the device data" CR));
|
|
return BLEdata;
|
|
}
|
|
|
|
double value = 9999;
|
|
value = value_from_service_data(servicedata, 28 + 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 23 + offset we determine the type of data we get from the sensor
|
|
switch (servicedata[23 + 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(servicedata, 28 + offset, 4);
|
|
BLEdata.set("tem", (double)value / 10);
|
|
// temperature
|
|
value = value_from_service_data(servicedata, 32 + offset, 4);
|
|
BLEdata.set("hum", (double)value / 10);
|
|
break;
|
|
default:
|
|
Log.trace(F("can't read values" CR));
|
|
}
|
|
|
|
return BLEdata;
|
|
}
|
|
|
|
JsonObject& process_scale_v1(JsonObject& BLEdata) {
|
|
const char* servicedata = BLEdata["servicedata"].as<const char*>();
|
|
|
|
double weight = value_from_service_data(servicedata, 2, 4) / 200;
|
|
|
|
//Set Json value
|
|
BLEdata.set("weight", (double)weight);
|
|
|
|
return BLEdata;
|
|
}
|
|
|
|
JsonObject& process_scale_v2(JsonObject& BLEdata) {
|
|
const char* servicedata = BLEdata["servicedata"].as<const char*>();
|
|
|
|
double weight = value_from_service_data(servicedata, 22, 4) / 200;
|
|
double impedance = value_from_service_data(servicedata, 18, 4);
|
|
|
|
//Set Json values
|
|
BLEdata.set("weight", (double)weight);
|
|
BLEdata.set("impedance", (double)impedance);
|
|
|
|
return BLEdata;
|
|
}
|
|
|
|
JsonObject& process_miband(JsonObject& BLEdata) {
|
|
const char* servicedata = BLEdata["servicedata"].as<const char*>();
|
|
|
|
double steps = value_from_service_data(servicedata, 0, 4);
|
|
|
|
//Set Json value
|
|
BLEdata.set("steps", (double)steps);
|
|
|
|
return BLEdata;
|
|
}
|
|
|
|
JsonObject& process_milamp(JsonObject& BLEdata) {
|
|
const char* servicedata = BLEdata["servicedata"].as<const char*>();
|
|
|
|
long darkness = value_from_service_data(servicedata, 8, 2);
|
|
|
|
//Set Json value
|
|
BLEdata.set("presence", (bool)"true");
|
|
BLEdata.set("darkness", (long)darkness);
|
|
|
|
return BLEdata;
|
|
}
|
|
|
|
JsonObject& process_cleargrass(JsonObject& BLEdata, boolean air) {
|
|
const char* servicedata = BLEdata["servicedata"].as<const char*>();
|
|
|
|
double value = 9999;
|
|
// humidity
|
|
value = value_from_service_data(servicedata, 20, 4);
|
|
BLEdata.set("tem", (double)value / 10);
|
|
// temperature
|
|
value = value_from_service_data(servicedata, 24, 4);
|
|
BLEdata.set("hum", (double)value / 10);
|
|
if (air) {
|
|
// air pressure
|
|
value = value_from_service_data(servicedata, 32, 4);
|
|
BLEdata.set("pres", (double)value / 100);
|
|
}
|
|
|
|
return BLEdata;
|
|
}
|
|
|
|
# ifdef subjectHomePresence
|
|
void haRoomPresence(JsonObject& HomePresence) {
|
|
int BLErssi = HomePresence["rssi"];
|
|
Log.trace(F("BLErssi %d" CR), BLErssi);
|
|
int txPower = HomePresence["txpower"] | 0;
|
|
if (txPower >= 0)
|
|
txPower = -59; //if tx power is not found we set a default calibration value
|
|
Log.trace(F("TxPower: %d" CR), 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;
|
|
Log.trace(F("Ble distance %D" CR), 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)) {
|
|
Log.trace(F("MQTTtoBT json set" CR));
|
|
|
|
// Black list & white list set
|
|
bool WorBupdated;
|
|
WorBupdated |= updateWorB(BTdata, true);
|
|
WorBupdated |= updateWorB(BTdata, false);
|
|
|
|
if (WorBupdated)
|
|
dumpDevices();
|
|
|
|
// Scan interval set
|
|
if (BTdata.containsKey("interval")) {
|
|
Log.trace(F("BLE interval setup" CR));
|
|
// storing BLE interval for further use if needed
|
|
unsigned int prevBLEinterval = BLEinterval;
|
|
Log.trace(F("Previous interval: %d ms" CR), BLEinterval);
|
|
// set BLE interval if present if not setting default value
|
|
BLEinterval = (unsigned int)BTdata["interval"];
|
|
Log.notice(F("New interval: %d ms" CR), BLEinterval);
|
|
if (BLEinterval == 0) {
|
|
if (BTtoMQTT()) // as BLEinterval is = to 0 we can launch the loop and the scan will execute immediately
|
|
Log.trace(F("Scan done" CR));
|
|
BLEinterval = prevBLEinterval; // as 0 was just used as a command we recover previous scan duration
|
|
}
|
|
}
|
|
// MinRSSI set
|
|
if (BTdata.containsKey("minrssi")) {
|
|
// storing Min RSSI for further use if needed
|
|
Log.trace(F("Previous Minrssi: %d" CR), Minrssi);
|
|
// set Min RSSI if present if not setting default value
|
|
Minrssi = (unsigned int)BTdata["minrssi"];
|
|
Log.notice(F("New Minrssi: %d" CR), Minrssi);
|
|
}
|
|
# ifdef ESP32
|
|
if (BTdata.containsKey("low_power_mode")) {
|
|
changelow_power_mode((int)BTdata["low_power_mode"]);
|
|
}
|
|
# endif
|
|
}
|
|
}
|
|
#endif
|