Files
OpenMQTT-Gateway/main/ZgatewayBT.ino
T
Florian 10f0d4cff4 Theengs decoder integration (#1092)
* Use Theengs Decoder for BLE device decoding
2021-10-31 16:51:05 -05:00

1301 lines
53 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"
SemaphoreHandle_t semaphoreCreateOrUpdateDevice;
// Headers used for deep sleep functions
# include <NimBLEAdvertisedDevice.h>
# include <NimBLEDevice.h>
# include <NimBLEScan.h>
# include <NimBLEUtils.h>
# include <driver/adc.h>
# include <esp_bt.h>
# include <esp_bt_main.h>
# include <esp_wifi.h>
# include <stdatomic.h>
# include "ZgatewayBLEConnect.h"
# include "soc/timer_group_reg.h"
# include "soc/timer_group_struct.h"
# endif
# if !defined(ESP32) && !defined(ESP8266)
# include <ArduinoSTL.h>
# endif
# include <decoder.h>
# include <vector>
using namespace std;
# define device_flags_init 0 << 0
# define device_flags_isDisc 1 << 0
# define device_flags_isWhiteL 1 << 1
# define device_flags_isBlackL 1 << 2
# define device_flags_connect 1 << 3
TheengsDecoder decoder;
struct decompose {
int start;
int len;
bool reverse;
};
# ifdef ESP32
vector<BLEAction> BLEactions;
# endif
vector<BLEdevice*> devices;
int newDevices = 0;
static BLEdevice NO_DEVICE_FOUND = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false, false, false, false, UNKNOWN_MODEL};
static bool oneWhite = false;
int minRssi = abs(MinimumRSSI); //minimum rssi value
void pubBTMainCore(JsonObject& data, bool haPresenceEnabled = true) {
if (abs((int)data["rssi"] | 0) < minRssi && data.containsKey("id")) {
String mac_address = data["id"].as<const char*>();
mac_address.replace(":", "");
String mactopic = subjectBTtoMQTT + String("/") + mac_address;
pub((char*)mactopic.c_str(), data);
}
if (haPresenceEnabled && data.containsKey("distance")) {
if (data.containsKey("servicedatauuid"))
data.remove("servicedatauuid");
if (data.containsKey("servicedata"))
data.remove("servicedata");
String topic = String(Base_Topic) + "home_presence/" + String(gateway_name);
pub_custom_topic((char*)topic.c_str(), data, false);
}
}
class JsonBundle {
public:
StaticJsonDocument<JSON_MSG_BUFFER> buffer;
JsonObject object;
bool haPresence;
JsonObject& createObject(const char* json = NULL, bool haPresenceEnabled = true) {
buffer.clear();
haPresence = haPresenceEnabled;
object = buffer.to<JsonObject>();
if (json != nullptr) {
auto error = deserializeJson(buffer, json);
if (error) {
Log.error(F("deserialize object failed: %s" CR), error.c_str());
}
}
return object;
}
};
void PublishDeviceData(JsonObject& BLEdata, bool processBLEData = true);
# ifdef ESP32
static TaskHandle_t xCoreTaskHandle;
atomic_int forceBTScan;
JsonBundle jsonBTBufferQueue[BTQueueSize];
atomic_int jsonBTBufferQueueNext, jsonBTBufferQueueLast;
int btQueueBlocked = 0;
int btQueueLengthSum = 0;
int btQueueLengthCount = 0;
JsonObject& getBTJsonObject(const char* json, bool haPresenceEnabled) {
int next, last;
for (bool blocked = false;;) {
next = atomic_load_explicit(&jsonBTBufferQueueNext, ::memory_order_seq_cst); // use namespace std -> ambiguous error...
last = atomic_load_explicit(&jsonBTBufferQueueLast, ::memory_order_seq_cst); // use namespace std -> ambiguous error...
if ((2 * BTQueueSize + last - next) % (2 * BTQueueSize) != BTQueueSize) break;
if (!blocked) {
blocked = true;
btQueueBlocked++;
}
delay(1);
}
return jsonBTBufferQueue[last % BTQueueSize].createObject(json, haPresenceEnabled);
}
// should run from the BT core
void pubBT(JsonObject& data) {
int last = atomic_load_explicit(&jsonBTBufferQueueLast, ::memory_order_seq_cst);
atomic_store_explicit(&jsonBTBufferQueueLast, (last + 1) % (2 * BTQueueSize), ::memory_order_seq_cst); // use namespace std -> ambiguous error...
}
// should run from the main core
void emptyBTQueue() {
for (bool first = true;;) {
int next = atomic_load_explicit(&jsonBTBufferQueueNext, ::memory_order_seq_cst); // use namespace std -> ambiguous error...
int last = atomic_load_explicit(&jsonBTBufferQueueLast, ::memory_order_seq_cst); // use namespace std -> ambiguous error...
if (last == next) break;
if (first) {
int diff = (2 * BTQueueSize + last - next) % (2 * BTQueueSize);
btQueueLengthSum += diff;
btQueueLengthCount++;
first = false;
}
JsonBundle& bundle = jsonBTBufferQueue[next % BTQueueSize];
pubBTMainCore(bundle.object, bundle.haPresence);
atomic_store_explicit(&jsonBTBufferQueueNext, (next + 1) % (2 * BTQueueSize), ::memory_order_seq_cst); // use namespace std -> ambiguous error...
vTaskDelay(1);
}
}
# else
JsonBundle jsonBTBuffer;
JsonObject& getBTJsonObject(const char* json, bool haPresenceEnabled) {
return jsonBTBuffer.createObject();
}
void pubBT(JsonObject& data) {
pubBTMainCore(data);
}
# endif
bool ProcessLock = false; // Process lock when we want to use a critical function like OTA for example
BLEdevice* getDeviceByMac(const char* mac);
void createOrUpdateDevice(const char* mac, uint8_t flags, ble_sensor_model model);
BLEdevice* getDeviceByMac(const char* mac) {
Log.trace(F("getDeviceByMac %s" CR), mac);
for (vector<BLEdevice*>::iterator it = devices.begin(); it != devices.end(); ++it) {
if ((strcmp((*it)->macAdr, mac) == 0)) {
return *it;
}
}
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), UNKNOWN_MODEL);
}
return true;
}
void createOrUpdateDevice(const char* mac, uint8_t flags, ble_sensor_model model) {
# ifdef ESP32
if (xSemaphoreTake(semaphoreCreateOrUpdateDevice, pdMS_TO_TICKS(30000)) == pdFALSE) {
Log.error(F("Semaphore NOT taken" CR));
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;
device->connect = flags & device_flags_connect;
device->sensorModel = model;
devices.push_back(device);
newDevices++;
} else {
Log.trace(F("update %s" CR), mac);
if (flags & device_flags_isDisc) {
device->isDisc = true;
}
if (flags & device_flags_connect) {
device->connect = true;
}
if (model != UNKNOWN_MODEL) device->sensorModel = model;
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
xSemaphoreGive(semaphoreCreateOrUpdateDevice);
# endif
}
# define isWhite(device) device->isWhtL
# define isBlack(device) device->isBlkL
# define isDiscovered(device) device->isDisc
void dumpDevices() {
for (vector<BLEdevice*>::iterator it = devices.begin(); it != devices.end(); ++it) {
BLEdevice* p = *it;
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);
Log.trace(F("sensorModel %d" CR), p->sensorModel);
}
}
void strupp(char* beg) {
while ((*beg = toupper(*beg)))
++beg;
}
# ifdef ZmqttDiscovery
void MiFloraDiscovery(const char* mac, const char* sensorModel) {
# define MiFloraparametersCount 5
Log.trace(F("MiFloraDiscovery" CR));
const char* MiFlorasensor[MiFloraparametersCount][9] = {
{"sensor", "MiFlora-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "MiFlora-lux", mac, "illuminance", jsonLux, "", "", "lx", stateClassMeasurement},
{"sensor", "MiFlora-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "MiFlora-fer", mac, "", jsonFer, "", "", "µS/cm", stateClassMeasurement},
{"sensor", "MiFlora-moi", mac, "", jsonMoi, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, MiFlorasensor, MiFloraparametersCount, "Mi-Flora", "Xiaomi", sensorModel);
}
void VegTrugDiscovery(const char* mac, const char* sensorModel) {
# define VegTrugparametersCount 4
Log.trace(F("VegTrugDiscovery" CR));
const char* VegTrugsensor[VegTrugparametersCount][9] = {
{"sensor", "VegTrug-lux", mac, "illuminance", jsonLux, "", "", "lx", stateClassMeasurement},
{"sensor", "VegTrug-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "VegTrug-fer", mac, "", jsonFer, "", "", "µS/cm", stateClassMeasurement},
{"sensor", "VegTrug-moi", mac, "", jsonMoi, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, VegTrugsensor, VegTrugparametersCount, "VegTrug", "VEGTRUG", sensorModel);
}
void MiJiaDiscovery(const char* mac, const char* sensorModel) {
# define MiJiaparametersCount 3
Log.trace(F("MiJiaDiscovery" CR));
const char* MiJiasensor[MiJiaparametersCount][9] = {
{"sensor", "MiJia-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "MiJia-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "MiJia-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, MiJiasensor, MiJiaparametersCount, "MiJia", "", sensorModel);
}
void FormalDiscovery(const char* mac, const char* sensorModel) {
# define FormalparametersCount 4
Log.trace(F("FormalDiscovery" CR));
const char* Formalsensor[FormalparametersCount][9] = {
{"sensor", "Formal-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "Formal-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "Formal-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement},
{"sensor", "Formal-for", mac, "", jsonFor, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, Formalsensor, FormalparametersCount, "Formal", "", sensorModel);
}
void LYWSD02Discovery(const char* mac, const char* sensorModel) {
# define LYWSD02parametersCount 3
Log.trace(F("LYWSD02Discovery" CR));
const char* LYWSD02sensor[LYWSD02parametersCount][9] = {
{"sensor", "LYWSD02-batt", mac, "battery", jsonBatt, "", "", "V", stateClassMeasurement},
{"sensor", "LYWSD02-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "LYWSD02-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, LYWSD02sensor, LYWSD02parametersCount, "LYWSD02", "Xiaomi", sensorModel);
}
void CLEARGRASSTRHDiscovery(const char* mac, const char* sensorModel) {
# define CLEARGRASSTRHparametersCount 3
Log.trace(F("CLEARGRASSTRHDiscovery" CR));
const char* CLEARGRASSTRHsensor[CLEARGRASSTRHparametersCount][9] = {
{"sensor", "CLEARGRASSTRH-batt", mac, "battery", jsonBatt, "", "", "V", stateClassMeasurement},
{"sensor", "CLEARGRASSTRH-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "CLEARGRASSTRH-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, CLEARGRASSTRHsensor, CLEARGRASSTRHparametersCount, "CLEARGRASSTRH", "ClearGrass", sensorModel);
}
void CLEARGRASSCGD1Discovery(const char* mac, const char* sensorModel) {
# define CLEARGRASSCGD1parametersCount 2
Log.trace(F("CLEARGRASSCGD1Discovery" CR));
const char* CLEARGRASSCGD1sensor[CLEARGRASSCGD1parametersCount][9] = {
{"sensor", "CLEARGRASSCGD1-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "CLEARGRASSCGD1-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, CLEARGRASSCGD1sensor, CLEARGRASSCGD1parametersCount, "CLEARGRASSCGD1", "ClearGrass", sensorModel);
}
void CLEARGRASSCGDK2Discovery(const char* mac, const char* sensorModel) {
# define CLEARGRASSCGDK2parametersCount 2
Log.trace(F("CLEARGRASSCGDK2Discovery" CR));
const char* CLEARGRASSCGDK2sensor[CLEARGRASSCGDK2parametersCount][9] = {
{"sensor", "CLEARGRASSCGDK2-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "CLEARGRASSCGDK2-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, CLEARGRASSCGDK2sensor, CLEARGRASSCGDK2parametersCount, "CLEARGRASSCGDK2", "ClearGrass", sensorModel);
}
void CLEARGRASSCGPR1Discovery(const char* mac, const char* sensorModel) {
# define CLEARGRASSCGPR1parametersCount 2
Log.trace(F("CLEARGRASSCGPR1Discovery" CR));
const char* CLEARGRASSCGPR1sensor[CLEARGRASSCGPR1parametersCount][9] = {
{"sensor", "CLEARGRASSCGPR1-pres", mac, "", jsonPres, "", "", "", stateClassMeasurement},
{"sensor", "CLEARGRASSCGPR1-lux", mac, "illuminance", jsonLux, "", "", "lx", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, CLEARGRASSCGPR1sensor, CLEARGRASSCGPR1parametersCount, "CLEARGRASSCGPR1", "ClearGrass", sensorModel);
}
void CLEARGRASSCGH1Discovery(const char* mac, const char* sensorModel) {
# define CLEARGRASSCGH1parametersCount 1
Log.trace(F("CLEARGRASSCGH1Discovery" CR));
const char* CLEARGRASSCGH1sensor[CLEARGRASSCGH1parametersCount][9] = {
{"binary_sensor", "CLEARGRASSCGH1-open", mac, "door", jsonOpen, "True", "False", "", stateClassNone},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, CLEARGRASSCGH1sensor, CLEARGRASSCGH1parametersCount, "CLEARGRASSCGH1", "ClearGrass", sensorModel);
}
void CLEARGRASSTRHKPADiscovery(const char* mac, const char* sensorModel) {
# define CLEARGRASSTRHKPAparametersCount 3
Log.trace(F("CLEARGRASSTRHKPADiscovery" CR));
const char* CLEARGRASSTRHKPAsensor[CLEARGRASSTRHKPAparametersCount][9] = {
{"sensor", "CLEARGRASSTRHKPA-pres", mac, "pressure", jsonPres, "", "", "kPa", stateClassMeasurement},
{"sensor", "CLEARGRASSTRHKPA-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "CLEARGRASSTRHKPA-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
Log.trace(F("CLEARGRASSTRHKPADiscovery %s" CR), sensorModel);
createDiscoveryFromList(mac, CLEARGRASSTRHKPAsensor, CLEARGRASSTRHKPAparametersCount, "CLEARGRASSTRHKPA", "ClearGrass", sensorModel);
}
void MiScaleDiscovery(const char* mac, const char* sensorModel) {
# define MiScaleparametersCount 1
Log.trace(F("MiScaleDiscovery" CR));
const char* MiScalesensor[MiScaleparametersCount][9] = {
{"sensor", "MiScale-weight", mac, "", jsonWeight, "", "", "kg", stateClassMeasurement},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, MiScalesensor, MiScaleparametersCount, "MiScale", "Xiaomi", sensorModel);
}
void MiLampDiscovery(const char* mac, const char* sensorModel) {
# define MiLampparametersCount 1
Log.trace(F("MiLampDiscovery" CR));
const char* MiLampsensor[MiLampparametersCount][9] = {
{"sensor", "MiLamp-presence", mac, "", jsonPresence, "", "", "d", stateClassNone},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, MiLampsensor, MiLampparametersCount, "MiLamp", "Xiaomi", sensorModel);
}
void MiBandDiscovery(const char* mac, const char* sensorModel) {
# define MiBandparametersCount 1
Log.trace(F("MiBandDiscovery" CR));
const char* MiBandsensor[MiBandparametersCount][9] = {
{"sensor", "MiBand-steps", mac, "", jsonStep, "", "", "nb", stateClassNone},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, MiBandsensor, MiBandparametersCount, "MiBand", "Xiaomi", sensorModel);
}
void InkBirdTH1Discovery(const char* mac, const char* sensorModel) {
# define InkBirdTH1parametersCount 3
Log.trace(F("InkBirdTH1Discovery" CR));
const char* InkBirdTH1sensor[InkBirdTH1parametersCount][9] = {
{"sensor", "InkBirdTH1-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "InkBirdTH1-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "InkBirdTH1-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, InkBirdTH1sensor, InkBirdTH1parametersCount, "IBS-TH1", "Inkbird", sensorModel);
}
void InkBirdTH2Discovery(const char* mac, const char* sensorModel) {
# define InkBirdTH2parametersCount 2
Log.trace(F("InkBirdTH2Discovery" CR));
const char* InkBirdTH2sensor[InkBirdTH2parametersCount][9] = {
{"sensor", "InkBirdTH2-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "InkBirdTH2-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, InkBirdTH2sensor, InkBirdTH2parametersCount, "IBS-TH2", "Inkbird", sensorModel);
}
void InkBird4XSDiscovery(const char* mac, const char* sensorModel) {
# define InkBird4XSparametersCount 4
Log.trace(F("InkBird4XSDiscovery" CR));
const char* InkBird4XSsensor[InkBird4XSparametersCount][9] = {
{"sensor", "InkBird4XS-temp1", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "InkBird4XS-temp2", mac, "temperature", jsonTempc2, "", "", "°C", stateClassMeasurement},
{"sensor", "InkBird4XS-temp3", mac, "temperature", jsonTempc3, "", "", "°C", stateClassMeasurement},
{"sensor", "InkBird4XS-temp4", mac, "temperature", jsonTempc4, "", "", "°C", stateClassMeasurement},
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, InkBird4XSsensor, InkBird4XSparametersCount, "IBT-4XS", "Inkbird", sensorModel);
}
void LYWSD03MMCDiscovery(const char* mac, const char* sensorModel) {
# define LYWSD03MMCparametersCount 4
Log.trace(F("LYWSD03MMCDiscovery" CR));
const char* LYWSD03MMCsensor[LYWSD03MMCparametersCount][9] = {
{"sensor", "LYWSD03MMC-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "LYWSD03MMC-volt", mac, "", jsonVolt, "", "", "V", stateClassMeasurement},
{"sensor", "LYWSD03MMC-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "LYWSD03MMC-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, LYWSD03MMCsensor, LYWSD03MMCparametersCount, "LYWSD03MMC", "Xiaomi", sensorModel);
}
void MHO_C401Discovery(const char* mac, const char* sensorModel) {
# define MHO_C401parametersCount 4
Log.trace(F("MHO_C401Discovery" CR));
const char* MHO_C401sensor[MHO_C401parametersCount][9] = {
{"sensor", "MHO_C401-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "MHO_C401-volt", mac, "", jsonVolt, "", "", "V", stateClassMeasurement},
{"sensor", "MHO_C401-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "MHO_C401-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, MHO_C401sensor, MHO_C401parametersCount, "MHO_C401", "Xiaomi", sensorModel);
}
void INodeEMDiscovery(const char* mac, const char* sensorModel) {
# define INodeEMparametersCount 3
Log.trace(F("INodeEMDiscovery" CR));
const char* INodeEMsensor[INodeEMparametersCount][9] = {
{"sensor", "iNodeEM-power", mac, "power", jsonPower, "", "", "W", stateClassMeasurement},
{"sensor", "iNodeEM-energy", mac, "", jsonEnergy, "", "", "kWh", stateClassMeasurement},
{"sensor", "iNodeEM-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, INodeEMsensor, INodeEMparametersCount, "INode-Energy-Meter", "INode", sensorModel);
}
void WS02Discovery(const char* mac, const char* sensorModel) {
# define WS02parametersCount 3
Log.trace(F("WS02Discovery" CR));
const char* WS02sensor[WS02parametersCount][9] = {
{"sensor", "WS02-volt", mac, "", jsonVolt, "", "", "V", stateClassMeasurement},
{"sensor", "WS02-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "WS02-hum", mac, "humidity", jsonHum, "", "", "%", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, WS02sensor, WS02parametersCount, "WS02", "SensorBlue", sensorModel);
}
void DT24Discovery(const char* mac, const char* sensorModel) {
# define DT24parametersCount 6
Log.trace(F("DT24Discovery" CR));
const char* DT24sensor[DT24parametersCount][9] = {
{"sensor", "DT24-volt", mac, "power", jsonVolt, "", "", "V", stateClassMeasurement},
{"sensor", "DT24-amp", mac, "power", jsonCurrent, "", "", "A", stateClassMeasurement},
{"sensor", "DT24-watt", mac, "power", jsonPower, "", "", "W", stateClassMeasurement},
{"sensor", "DT24-watt-hour", mac, "power", jsonEnergy, "", "", "kWh", stateClassMeasurement},
{"sensor", "DT24-price", mac, "", jsonMsg, "", "", "", stateClassNone},
{"sensor", "DT24-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, DT24sensor, DT24parametersCount, "DT24", "ATorch", sensorModel);
}
void EddystoneTLMDiscovery(const char* mac, const char* sensorModel) {
# define EddystoneTLMparametersCount 4
Log.trace(F("EddystoneTLMDiscovery" CR));
const char* EddystoneTLMsensor[EddystoneTLMparametersCount][9] = {
{"sensor", "EddystoneTLM-volt", mac, "", jsonVolt, "", "", "V", stateClassMeasurement},
{"sensor", "EddystoneTLM-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "EddystoneTLM-count", mac, "", jsonCount, "", "", "", stateClassTotal},
{"sensor", "EddystoneTLM-time", mac, "", jsonTime, "", "", "", stateClassNone}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, EddystoneTLMsensor, EddystoneTLMparametersCount, "EddystoneTLM", "SensorBlue", sensorModel);
}
void TPMSDiscovery(const char* mac, const char* sensorModel) {
# define TPMSparametersCount 5
Log.trace(F("TPMSDiscovery" CR));
const char* TPMSsensor[TPMSparametersCount][9] = {
{"sensor", "TPMS-batt", mac, "battery", jsonBatt, "", "", "%", stateClassMeasurement},
{"sensor", "TPMS-temp", mac, "temperature", jsonTempc, "", "", "°C", stateClassMeasurement},
{"sensor", "TPMS-pres", mac, "pressure", jsonPres, "", "", "kPa", stateClassMeasurement},
{"sensor", "TPMS-count", mac, "", jsonCount, "", "", "", stateClassTotal},
{"sensor", "TPMS-alarm", mac, "", jsonAlarm, "", "", "", stateClassNone}
//component type,name,availability topic,device class,value template,payload on, payload off, unit of measurement
};
createDiscoveryFromList(mac, TPMSsensor, TPMSparametersCount, "TPMS", "TPMS", sensorModel);
}
# else
void MiFloraDiscovery(const char* mac, const char* sensorModel) {}
void VegTrugDiscovery(const char* mac, const char* sensorModel) {}
void MiJiaDiscovery(const char* mac, const char* sensorModel) {}
void FormalDiscovery(const char* mac, const char* sensorModel) {}
void LYWSD02Discovery(const char* mac, const char* sensorModel) {}
void CLEARGRASSTRHDiscovery(const char* mac, const char* sensorModel) {}
void CLEARGRASSCGD1Discovery(const char* mac, const char* sensorModel) {}
void CLEARGRASSCGDK2Discovery(const char* mac, const char* sensorModel) {}
void CLEARGRASSCGPR1Discovery(const char* mac, const char* sensorModel) {}
void CLEARGRASSCGH1Discovery(const char* mac, const char* sensorModel) {}
void CLEARGRASSTRHKPADiscovery(const char* mac, const char* sensorModel) {}
void MiScaleDiscovery(const char* mac, const char* sensorModel) {}
void MiLampDiscovery(const char* mac, const char* sensorModel) {}
void MiBandDiscovery(const char* mac, const char* sensorModel) {}
void InkBirdTH1Discovery(const char* mac, const char* sensorModel) {}
void InkBirdTH2Discovery(const char* mac, const char* sensorModel) {}
void InkBird4XSDiscovery(const char* mac, const char* sensorModel) {}
void LYWSD03MMCDiscovery(const char* mac, const char* sensorModel) {}
void MHO_C401Discovery(const char* mac, const char* sensorModel) {}
void INodeEMDiscovery(const char* mac, const char* sensorModel) {}
void WS02Discovery(const char* mac, const char* sensorModel) {}
void DT24Discovery(const char* mac, const char* sensorModel) {}
void EddystoneTLMDiscovery(const char* mac, const char* sensorModel) {}
void TPMSDiscovery(const char* mac, const char* sensorModel) {}
# 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/
//core on which the BLE detection task will run
static int taskCore = 0;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
std::string convertServiceData(std::string deviceServiceData) {
int serviceDataLength = (int)deviceServiceData.length();
char spr[2 * serviceDataLength + 1];
for (int i = 0; i < serviceDataLength; i++) sprintf(spr + 2 * i, "%.2x", (unsigned char)deviceServiceData[i]);
spr[2 * serviceDataLength] = 0;
Log.trace("Converted service data (%d) to %s" CR, serviceDataLength, spr);
return spr;
}
void onResult(BLEAdvertisedDevice* advertisedDevice) {
if (!ProcessLock) {
Log.trace(F("Creating BLE buffer" CR));
JsonObject& BLEdata = getBTJsonObject();
String mac_adress = advertisedDevice->getAddress().toString().c_str();
mac_adress.toUpperCase();
BLEdata["id"] = (char*)mac_adress.c_str();
Log.notice(F("Device detected: %s" CR), (char*)mac_adress.c_str());
BLEdevice* device = getDeviceByMac(BLEdata["id"].as<const char*>());
# if BLE_FILTER_CONNECTABLE
if (device->connect) {
Log.notice(F("Filtered connectable device" CR));
return;
}
# endif
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["name"] = (char*)advertisedDevice->getName().c_str();
if (advertisedDevice->haveManufacturerData()) {
char* manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t*)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length());
BLEdata["manufacturerdata"] = manufacturerdata;
}
if (advertisedDevice->haveRSSI())
BLEdata["rssi"] = (int)advertisedDevice->getRSSI();
if (advertisedDevice->haveTXPower())
BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower();
if (advertisedDevice->haveRSSI() && !publishOnlySensors && hassPresence) {
hass_presence(BLEdata); // this device has an rssi and we don't want only sensors so in consequence we can use it for home assistant room presence component
}
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 service_data = convertServiceData(advertisedDevice->getServiceData(j));
Log.trace(F("Service data: %s" CR), service_data.c_str());
BLEdata["servicedata"] = (char*)service_data.c_str();
std::string serviceDatauuid = advertisedDevice->getServiceDataUUID(j).toString();
Log.trace(F("Service data UUID: %s" CR), (char*)serviceDatauuid.c_str());
BLEdata["servicedatauuid"] = (char*)serviceDatauuid.c_str();
process_bledata(BLEdata); // this will force to resolve all the service data
}
PublishDeviceData(BLEdata, false);
} else {
PublishDeviceData(BLEdata); // PublishDeviceData has its own logic whether it needs to publish the json or not
}
} else {
Log.trace(F("Filtered mac device" CR));
}
}
}
};
/**
* BLEscan used to retrieve BLE advertized data from devices without connection
*/
void BLEscan() {
disableCore0WDT();
Log.notice(F("Scan begin" CR));
BLEScan* pBLEScan = BLEDevice::getScan();
MyAdvertisedDeviceCallbacks myCallbacks;
pBLEScan->setAdvertisedDeviceCallbacks(&myCallbacks);
pBLEScan->setActiveScan(ActiveBLEScan);
pBLEScan->setInterval(BLEScanInterval);
pBLEScan->setWindow(BLEScanWindow);
BLEScanResults foundDevices = pBLEScan->start(Scan_duration / 1000, false);
scanCount++;
Log.notice(F("Found %d devices, scan number %d end" CR), foundDevices.getCount(), scanCount);
enableCore0WDT();
}
/**
* Connect to BLE devices and initiate the callbacks with a service/characteristic request
*/
void BLEconnect() {
Log.notice(F("BLE Connect begin" CR));
for (vector<BLEdevice*>::iterator it = devices.begin(); it != devices.end(); ++it) {
BLEdevice* p = *it;
if (p->connect) {
Log.trace(F("Model to connect found: %s" CR), p->macAdr);
NimBLEAddress addr(std::string(p->macAdr));
switch (p->sensorModel) {
case LYWSD03MMC:
case MHO_C401: {
LYWSD03MMC_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
break;
}
case DT24: {
DT24_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
break;
}
case GENERIC: {
GENERIC_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
break;
}
case HHCCJCY01HHCC: {
HHCCJCY01HHCC_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
break;
}
default:
break;
}
if (BLEactions.size() > 0) {
std::vector<BLEAction> swap;
for (auto& it : BLEactions) {
if (!it.complete && --it.ttl) {
swap.push_back(it);
}
}
std::swap(BLEactions, swap);
}
}
}
Log.notice(F("BLE Connect end" CR));
}
void stopProcessing() {
Log.notice(F("Stop BLE processing" CR));
ProcessLock = true;
delay(Scan_duration < 2000 ? Scan_duration : 2000);
}
void startProcessing() {
Log.notice(F("Start BLE processing" CR));
ProcessLock = false;
vTaskResume(xCoreTaskHandle);
}
void coreTask(void* pvParameters) {
while (true) {
if (!ProcessLock) {
int n = 0;
while (client.state() != 0 && n <= InitialMQTTConnectionTimeout && !ProcessLock) {
n++;
delay(1000);
}
if (client.state() != 0) {
Log.warning(F("MQTT client disconnected no BLE scan" CR));
} else if (!ProcessLock) {
BLEscan();
// Launching a connect every BLEscanBeforeConnect
if ((!(scanCount % BLEscanBeforeConnect) || scanCount == 1) && bleConnect)
BLEconnect();
dumpDevices();
}
if (lowpowermode) {
lowPowerESP32();
int scan = atomic_exchange_explicit(&forceBTScan, 0, ::memory_order_seq_cst); // is this enough, it will wait the full deepsleep...
if (scan == 1) BTforceScan();
} else {
for (int interval = BLEinterval, waitms; interval > 0; interval -= waitms) {
int scan = atomic_exchange_explicit(&forceBTScan, 0, ::memory_order_seq_cst);
if (scan == 1) BTforceScan(); // should we break after this?
delay(waitms = interval > 100 ? 100 : interval); // 100ms
}
}
} else {
Log.trace(F("BLE core task canceled by processLock" CR));
vTaskSuspend(xCoreTaskHandle);
}
}
}
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) || defined(ZboardM5STICKCP)
sleepScreen();
esp_sleep_enable_ext0_wakeup((gpio_num_t)SLEEP_BUTTON, LOW);
# endif
Log.trace(F("Deactivating ESP32 components" CR));
BLEDevice::deinit(true);
esp_bt_mem_release(ESP_BT_MODE_BTDM);
// Ignore the deprecated warning, this call is necessary here.
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
adc_power_off();
# pragma GCC diagnostic pop
esp_wifi_stop();
esp_deep_sleep(time_in_us);
}
void changelowpowermode(int newLowPowerMode) {
Log.notice(F("Changing LOW POWER mode to: %d" CR), newLowPowerMode);
# if defined(ZboardM5STACK) || defined(ZboardM5STICKC) || defined(ZboardM5STICKCP)
if (lowpowermode == 2) {
# ifdef ZboardM5STACK
M5.Lcd.wakeup();
# endif
# if defined(ZboardM5STICKC) || defined(ZboardM5STICKCP)
M5.Axp.SetLDO2(true);
M5.Lcd.begin();
# endif
}
char lpm[2];
sprintf(lpm, "%d", newLowPowerMode);
M5Display("Changing LOW POWER mode to:", lpm, "");
# endif
lowpowermode = newLowPowerMode;
preferences.begin(Gateway_Short_Name, false);
preferences.putUInt("lowpowermode", lowpowermode);
preferences.end();
}
void setupBT() {
Log.notice(F("BLE scans interval: %d" CR), BLEinterval);
Log.notice(F("BLE scans number before connect: %d" CR), BLEscanBeforeConnect);
Log.notice(F("Publishing only BLE sensors: %T" CR), publishOnlySensors);
Log.notice(F("minrssi: %d" CR), minRssi);
Log.notice(F("Low Power Mode: %d" CR), lowpowermode);
atomic_init(&forceBTScan, 0); // in theory, we don't need this
atomic_init(&jsonBTBufferQueueNext, 0); // in theory, we don't need this
atomic_init(&jsonBTBufferQueueLast, 0); // in theory, we don't need this
semaphoreCreateOrUpdateDevice = xSemaphoreCreateBinary();
xSemaphoreGive(semaphoreCreateOrUpdateDevice);
BLEDevice::setScanDuplicateCacheSize(BLEScanDuplicateCacheSize);
BLEDevice::init("");
// 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 */
&xCoreTaskHandle, /* 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();
return true;
}
# else // arduino or ESP8266 working with HM10/11
# include <SoftwareSerial.h>
# define QUESTION_MSG "AT+DISA?"
SoftwareSerial softserial(BT_RX, BT_TX);
String returnedString((char*)0);
unsigned long timebt = 0;
// this struct define which parts of the hexadecimal chain we extract and what to do with these parts
// {"mac"}, {"typ"}, {"rsi"}, {"rdl"}, {"sty"}, {"rda"}
struct decompose d[6] = {{0, 12, true}, {12, 2, false}, {14, 2, false}, {16, 2, false}, {28, 4, true}, {32, 60, false}};
void setupBT() {
Log.notice(F("BLE interval: %d" CR), BLEinterval);
Log.notice(F("BLE scans number before connect: %d" CR), BLEscanBeforeConnect);
Log.notice(F("Publishing only BLE sensors: %T" CR), publishOnlySensors);
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);
# if defined(ESP8266)
returnedString.reserve(512); //reserve memory space for BT Serial. (size should depend on available RAM)
# endif
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 += "0";
}
returnedString += String(a, HEX);
}
if (millis() > (timebt + BLEinterval)) { //retrieving data
timebt = millis();
returnedString.remove(0); //init data string
softserial.print(F(QUESTION_MSG)); //start new discovery
return false;
}
# if defined(ESP8266)
yield();
# endif
if (returnedString.length() > (BLEdelimiterLength + CRLR_Length)) { //packet has to be at least the (BLEdelimiter + 'CR LF') length
Log.verbose(F("returnedString: %s" CR), (char*)returnedString.c_str());
if (returnedString.equals(F(BLEEndOfDiscovery))) //OK+DISCE
{
returnedString.remove(0); //clear data string
scanCount++;
Log.notice(F("Scan number %d end " CR), scanCount);
return false;
}
size_t pos = 0, eolPos = 0;
while ((pos = returnedString.indexOf(F(BLEdelimiter))) != -1 && (eolPos = returnedString.indexOf(F(CRLR))) != -1) {
# if defined(ESP8266)
yield();
# endif
String token = returnedString.substring(pos + BLEdelimiterLength, eolPos); //capture a BT device frame
returnedString.remove(0, eolPos + CRLR_Length); //remove frame from main buffer (including 'CR LF' chars)
Log.trace(F("Token: %s" CR), token.c_str());
if (token.length() > 32) { // we extract data only if we have size is at least the size of (MAC, TYPE, RSSI, and Rest Data Length)
String mac = F("");
mac.reserve(17);
for (int i = d[0].start; i < (d[0].start + d[0].len); i += 2) {
mac += token.substring((d[0].start + d[0].len) - i - 2, (d[0].start + d[0].len) - i);
if (i < (d[0].start + d[0].len) - 2)
mac += ":";
}
mac.toUpperCase();
String rssiStr = token.substring(d[2].start, (d[2].start + d[2].len));
int rssi = (int)strtol(rssiStr.c_str(), NULL, 16) - 256;
String restDataLengthStr = token.substring(d[3].start, (d[3].start + d[3].len));
int restDataLength = (int)strtol(restDataLengthStr.c_str(), NULL, 16) * 2;
String restData = F("");
if (restDataLength <= 60)
restData = token.substring(d[5].start, (d[5].start + restDataLength));
Log.trace(F("Creating BLE buffer" CR));
JsonObject& BLEdata = getBTJsonObject();
Log.trace(F("Id %s" CR), (char*)mac.c_str());
BLEdata["id"] = (const char*)mac.c_str();
BLEdevice* device = getDeviceByMac((char*)mac.c_str());
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
BLEdata["rssi"] = (int)rssi;
if (!publishOnlySensors && hassPresence)
hass_presence(BLEdata); // this device has an rssi and we don't want only sensors so in consequence we can use it for home assistant room presence component
Log.trace(F("Service data: %s" CR), restData.c_str());
BLEdata["servicedata"] = restData.c_str();
PublishDeviceData(BLEdata);
}
}
return false;
}
}
# endif
void RemoveJsonPropertyIf(JsonObject& obj, const char* key, bool condition) {
if (condition) {
Log.trace(F("Removing %s" CR), key);
obj.remove(key);
}
}
boolean valid_service_data(const char* data, int size) {
for (int i = 0; i < size; ++i) {
if (data[i] != 48) // 48 correspond to 0 in ASCII table
return true;
}
return false;
}
// This function always should be called from the main core as it generates direct mqtt messages
void launchBTDiscovery() {
if (newDevices == 0)
return;
# ifdef ESP32
if (xSemaphoreTake(semaphoreCreateOrUpdateDevice, pdMS_TO_TICKS(1000) == pdFALSE)) {
Log.error(F("Semaphore NOT taken" CR));
return;
}
newDevices = 0;
vector<BLEdevice*> localDevices = devices;
xSemaphoreGive(semaphoreCreateOrUpdateDevice);
for (vector<BLEdevice*>::iterator it = localDevices.begin(); it != localDevices.end(); ++it) {
# else
newDevices = 0;
for (vector<BLEdevice*>::iterator it = devices.begin(); it != devices.end(); ++it) {
# endif
BLEdevice* p = *it;
if (p->sensorModel != UNKNOWN_MODEL && !isDiscovered(p)) {
String macWOdots = String(p->macAdr);
macWOdots.replace(":", "");
Log.trace(F("Launching discovery of %s" CR), p->macAdr);
if (p->sensorModel == HHCCJCY01HHCC) MiFloraDiscovery(macWOdots.c_str(), "HHCCJCY01HHCC");
if (p->sensorModel == VEGTRUG) VegTrugDiscovery(macWOdots.c_str(), "VEGTRUG");
if (p->sensorModel == LYWSDCGQ) MiJiaDiscovery(macWOdots.c_str(), "LYWSDCGQ");
if (p->sensorModel == JQJCY01YM) FormalDiscovery(macWOdots.c_str(), "JQJCY01YM");
if (p->sensorModel == LYWSD02) LYWSD02Discovery(macWOdots.c_str(), "LYWSD02");
if (p->sensorModel == CGG1) CLEARGRASSTRHDiscovery(macWOdots.c_str(), "CGG1");
if (p->sensorModel == CGP1W) CLEARGRASSTRHKPADiscovery(macWOdots.c_str(), "CGP1W");
if (p->sensorModel == MUE4094RT) MiLampDiscovery(macWOdots.c_str(), "MUE4094RT");
if (p->sensorModel == CGDK2) CLEARGRASSCGDK2Discovery(macWOdots.c_str(), "CGDK2");
if (p->sensorModel == CGPR1) CLEARGRASSCGPR1Discovery(macWOdots.c_str(), "CGPR1");
if (p->sensorModel == CGH1) CLEARGRASSCGH1Discovery(macWOdots.c_str(), "CGH1");
if (p->sensorModel == CGD1) CLEARGRASSCGD1Discovery(macWOdots.c_str(), "CGD1");
if (p->sensorModel == WS02) WS02Discovery(macWOdots.c_str(), "WS02");
if (p->sensorModel == EDDYSTONE_TLM) EddystoneTLMDiscovery(macWOdots.c_str(), "EDDYSTONE_TLM");
if (p->sensorModel == MIBAND) MiBandDiscovery(macWOdots.c_str(), "MIBAND");
if ((p->sensorModel == XMTZC04HM) ||
(p->sensorModel == XMTZC05HM)) MiScaleDiscovery(macWOdots.c_str(), "XMTZC0xHM");
if (p->sensorModel == IBSTH1) InkBirdTH1Discovery(macWOdots.c_str(), "IBS-TH1");
if (p->sensorModel == IBSTH2) InkBirdTH2Discovery(macWOdots.c_str(), "IBS-TH2");
if (p->sensorModel == IBT4XS) InkBird4XSDiscovery(macWOdots.c_str(), "IBT-4XS");
if (p->sensorModel == LYWSD03MMC || p->sensorModel == LYWSD03MMC_ATC || p->sensorModel == LYWSD03MMC_PVVX) LYWSD03MMCDiscovery(macWOdots.c_str(), "LYWSD03MMC");
if (p->sensorModel == MHO_C401) MHO_C401Discovery(macWOdots.c_str(), "MHO_C401");
if (p->sensorModel == INODE_EM) INodeEMDiscovery(macWOdots.c_str(), "INODE_EM");
if (p->sensorModel == DT24) DT24Discovery(macWOdots.c_str(), "DT24-BLE");
if (p->sensorModel == TPMS) TPMSDiscovery(macWOdots.c_str(), "TPMS");
p->isDisc = true; // we don't need the semaphore and all the search magic via createOrUpdateDevice
} else {
if (!isDiscovered(p)) {
Log.trace(F("Device UNKNOWN_MODEL %s" CR), p->macAdr);
p->isDisc = true;
} else {
Log.trace(F("Device already discovered %s" CR), p->macAdr);
}
}
}
}
void PublishDeviceData(JsonObject& BLEdata, bool processBLEData) {
if (abs((int)BLEdata["rssi"] | 0) < minRssi) { // process only the devices close enough
if (processBLEData) process_bledata(BLEdata);
if (!publishOnlySensors || BLEdata.containsKey("model") || BLEdata.containsKey("distance")) {
# if !pubBLEServiceUUID
RemoveJsonPropertyIf(BLEdata, "servicedatauuid", BLEdata.containsKey("servicedatauuid"));
# endif
# if !pubKnownBLEServiceData
RemoveJsonPropertyIf(BLEdata, "servicedata", BLEdata.containsKey("model") && BLEdata.containsKey("servicedata"));
# endif
# if !pubBLEManufacturerData
RemoveJsonPropertyIf(BLEdata, "manufacturerdata", BLEdata.containsKey("model") && BLEdata.containsKey("manufacturerdata"));
# endif
pubBT(BLEdata);
} else {
# if !pubUnknownBLEServiceData
Log.trace(F("Unknown service data, removing it" CR));
RemoveJsonPropertyIf(BLEdata, "servicedata", BLEdata.containsKey("servicedata"));
# endif
# if !pubUnknownBLEManufacturerData
RemoveJsonPropertyIf(BLEdata, "manufacturerdata", BLEdata.containsKey("model") && BLEdata.containsKey("manufacturerdata"));
# endif
}
} else if (BLEdata.containsKey("distance")) {
pubBT(BLEdata);
} else {
Log.trace(F("Low rssi, device filtered" CR));
}
}
void process_bledata(JsonObject& BLEdata) {
if (decoder.decodeBLEJson(BLEdata)) {
Log.trace(F("1decoder found device: %s" CR), BLEdata["model"].as<const char*>());
} else {
Log.trace(F("No device found " CR));
}
}
void hass_presence(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);
}
void BTforceScan() {
if (!ProcessLock) {
BTtoMQTT();
Log.trace(F("Scan done" CR));
# ifdef ESP32
if (bleConnect)
BLEconnect();
# endif
} else {
Log.trace(F("Cannot launch scan due to other process running" CR));
}
}
void MQTTtoBTAction(JsonObject& BTdata) {
# ifdef ESP32
BLEAction action;
action.ttl = BTdata.containsKey("ttl") ? (uint8_t)BTdata["ttl"] : 1;
action.value_type = BLE_VAL_STRING;
if (BTdata.containsKey("value_type")) {
String vt = BTdata["value_type"];
vt.toUpperCase();
if (vt == "HEX")
action.value_type = BLE_VAL_HEX;
else if (vt == "INT")
action.value_type = BLE_VAL_INT;
else if (vt == "FLOAT")
action.value_type = BLE_VAL_FLOAT;
else if (vt != "STRING") {
Log.error(F("BLE value type invalid %s" CR), vt.c_str());
return;
}
}
Log.trace(F("BLE ACTION TTL = %u" CR), action.ttl);
action.complete = false;
if (BTdata.containsKey("ble_write_address") &&
BTdata.containsKey("ble_write_service") &&
BTdata.containsKey("ble_write_char") &&
BTdata.containsKey("ble_write_value")) {
strcpy(action.addr, (const char*)BTdata["ble_write_address"]);
action.service = NimBLEUUID((const char*)BTdata["ble_write_service"]);
action.characteristic = NimBLEUUID((const char*)BTdata["ble_write_char"]);
action.value = std::string((const char*)BTdata["ble_write_value"]);
action.write = true;
Log.trace(F("BLE ACTION Write" CR));
} else if (BTdata.containsKey("ble_read_address") &&
BTdata.containsKey("ble_read_service") &&
BTdata.containsKey("ble_read_char")) {
strcpy(action.addr, (const char*)BTdata["ble_read_address"]);
action.service = NimBLEUUID((const char*)BTdata["ble_read_service"]);
action.characteristic = NimBLEUUID((const char*)BTdata["ble_read_char"]);
action.write = false;
Log.trace(F("BLE ACTION Read" CR));
} else {
return;
}
createOrUpdateDevice(action.addr, device_flags_connect, GENERIC);
BLEactions.push_back(action);
# 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) {
# ifdef ESP32
if (xSemaphoreTake(semaphoreCreateOrUpdateDevice, pdMS_TO_TICKS(1000) == pdTRUE)) {
dumpDevices();
xSemaphoreGive(semaphoreCreateOrUpdateDevice);
}
# else
dumpDevices();
# endif
}
// Scan interval set
if (BTdata.containsKey("interval")) {
Log.trace(F("BLE interval setup" CR));
unsigned int interval = BTdata["interval"];
if (interval == 0) {
# ifdef ESP32
atomic_store_explicit(&forceBTScan, 1, ::memory_order_seq_cst); // ask the other core to do the scan for us
# else
BTforceScan();
# endif
} else {
Log.trace(F("Previous interval: %d ms" CR), BLEinterval);
BLEinterval = interval;
Log.notice(F("New interval: %d ms" CR), BLEinterval);
}
}
// Number of scan before a connect set
if (BTdata.containsKey("scanbcnct")) {
Log.trace(F("BLE scans number before a connect setup" CR));
Log.trace(F("Previous number: %d" CR), BLEscanBeforeConnect);
BLEscanBeforeConnect = (unsigned int)BTdata["scanbcnct"];
Log.notice(F("New scan number before connect: %d" CR), BLEscanBeforeConnect);
}
// publish all BLE devices discovered or only the identified sensors (like temperature sensors)
if (BTdata.containsKey("onlysensors")) {
Log.trace(F("Do we publish only sensors" CR));
Log.trace(F("Previous value: %T" CR), publishOnlySensors);
publishOnlySensors = (bool)BTdata["onlysensors"];
Log.notice(F("New value onlysensors: %T" CR), publishOnlySensors);
}
# ifdef ESP32
// Attempts to connect to elligible devices or not
if (BTdata.containsKey("bleconnect")) {
Log.trace(F("Do we initiate a connection to retrieve data" CR));
Log.trace(F("Previous value: %T" CR), bleConnect);
bleConnect = (bool)BTdata["bleconnect"];
Log.notice(F("New value bleConnect: %T" CR), bleConnect);
}
if (BTdata.containsKey("lowpowermode")) {
changelowpowermode((int)BTdata["lowpowermode"]);
}
MQTTtoBTAction(BTdata);
# endif
// 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 = abs((int)BTdata["minrssi"]);
Log.notice(F("New minrssi: %d" CR), minRssi);
}
// Home Assistant presence message
if (BTdata.containsKey("hasspresence")) {
// storing Min RSSI for further use if needed
Log.trace(F("Previous hasspresence: %T" CR), hassPresence);
// set Min RSSI if present if not setting default value
hassPresence = (bool)BTdata["hasspresence"];
Log.notice(F("New hasspresence: %T" CR), hassPresence);
}
}
}
#endif