Add Switchbot support and discovery. (#995)

* Use a task to process immediate BLE actions to prevent MQTT disconnection if the action takes too long to complete.

* Add state_on and state_off parameters to createDiscovery
This commit is contained in:
h2zero authored and GitHub committed 2022-04-30 09:16:06 -05:00
1 parent a15a1c0851
commit 8d6acbee12
7 files changed
+222 -62

No files matched your search

+23
View File
@@ -217,6 +217,29 @@ The `ttl` parameter is the number of attempts to connect (defaults to 1), which
`value_type` can be one of: STRING, HEX, INT, FLOAT. Default is STRING if omitted in the message.
:::
## Switchbot S1 control (ESP32 only)
Switchbot S1 devices are automatically discovered and available as a device in the configuration menu of home assistant.
::: tip If the switchbot mode is changed the ESP32 must be restarted. :::
The device can also be controlled over MQTT with a simplified BLE write command.
### Example command to set the Switchbot state to ON:
```
mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoBT/config -m '{
"SBS1":"on",
"mac":"AA:BB:CC:DD:EE:FF"
}'
```
Response (assuming success):
```
{
"id":"AA:BB:CC:DD:EE:FF",
"state":"on"
}
```
## Other
To check your hm10 firmware version upload a serial sketch to the nodemcu (this will enable communication directly with the hm10) and launch the command:
+9
View File
@@ -66,5 +66,14 @@ public:
void publishData() override;
};
class SBS1_connect : public zBLEConnect {
uint8_t m_notifyVal;
void notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify);
public:
SBS1_connect(NimBLEAddress& addr) : zBLEConnect(addr) {}
bool processActions(std::vector<BLEAction>& actions) override;
};
#endif //ESP32
#endif //zBLEConnect_h
+72
View File
@@ -342,5 +342,77 @@ void XMWSDJ04MMC_connect::publishData() {
}
}
/*-----------------------SBS1 HANDLING-----------------------*/
void SBS1_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
if (m_taskHandle == nullptr) {
return; // unexpected notification
}
if (!ProcessLock) {
Log.trace(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str());
if (length) {
m_notifyVal = *pData;
} else {
Log.notice(F("Invalid notification data" CR));
return;
}
} else {
Log.trace(F("Callback process canceled by processLock" CR));
}
xTaskNotifyGive(m_taskHandle);
}
bool SBS1_connect::processActions(std::vector<BLEAction>& actions) {
NimBLEUUID serviceUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
NimBLEUUID charUUID("cba20002-224d-11e6-9fb8-0002a5d5c51b");
NimBLEUUID notifyCharUUID("cba20003-224d-11e6-9fb8-0002a5d5c51b");
static byte ON[] = {0x57, 0x01, 0x01};
static byte OFF[] = {0x57, 0x01, 0x02};
bool result = false;
if (actions.size() > 0) {
for (auto& it : actions) {
if (NimBLEAddress(it.addr) == m_pClient->getPeerAddress()) {
NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID);
NimBLERemoteCharacteristic* pNotifyChar = getCharacteristic(serviceUUID, notifyCharUUID);
if (it.write && pChar && pNotifyChar) {
Log.trace(F("processing Switchbot %s" CR), it.value.c_str());
if (pNotifyChar->subscribe(true,
std::bind(&SBS1_connect::notifyCB,
this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4),
true)) {
if (it.value == "on") {
result = pChar->writeValue(ON, 3, false);
} else {
result = pChar->writeValue(OFF, 3, false);
}
if (result) {
m_taskHandle = xTaskGetCurrentTaskHandle();
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) {
m_taskHandle = nullptr;
}
result = m_notifyVal == 0x01;
}
}
}
it.complete = result;
if (result || it.ttl <= 1) {
JsonObject BLEresult = getBTJsonObject();
BLEresult["id"] = it.addr;
BLEresult["state"] = result ? it.value : it.value == "on" ? "off" : "on";
pubBT(BLEresult);
}
}
}
}
return result;
}
# endif //ZgatewayBT
#endif //ESP32
+103 -51
View File
@@ -1,18 +1,18 @@
/*
OpenMQTTGateway - ESP8266 or Arduino program for home automation
/*
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
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
@@ -461,7 +461,7 @@ void procBLETask(void* pvParameters) {
}
}
/**
/**
* BLEscan used to retrieve BLE advertized data from devices without connection
*/
void BLEscan() {
@@ -484,7 +484,7 @@ void BLEscan() {
Log.trace(F("Process BLE stack free: %u" CR), uxTaskGetStackHighWaterMark(xProcBLETaskHandle));
}
/**
/**
* Connect to BLE devices and initiate the callbacks with a service/characteristic request
*/
void BLEconnect() {
@@ -513,6 +513,9 @@ void BLEconnect() {
XMWSDJ04MMC_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
BLEclient.publishData();
} else if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::SBS1) {
SBS1_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
} else {
GENERIC_connect BLEclient(addr);
if (BLEclient.processActions(BLEactions)) {
@@ -874,13 +877,25 @@ void launchBTDiscovery() {
# else
String value_template = "{{ value_json." + String(prop.key().c_str()) + " | is_defined }}";
# endif
createDiscovery("sensor",
discovery_topic.c_str(), entity_name.c_str(), unique_id.c_str(),
will_Topic, prop.value()["name"], value_template.c_str(),
"", "", prop.value()["unit"],
0, "", "", false, "",
model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false,
stateClassMeasurement);
if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::SBS1 && !strcmp(prop.key().c_str(), "state")) {
String payload_on = "{\"SBS1\":\"on\",\"mac\":\"" + String(p->macAdr) + "\"}";
String payload_off = "{\"SBS1\":\"off\",\"mac\":\"" + String(p->macAdr) + "\"}";
createDiscovery("switch", //set Type
discovery_topic.c_str(), entity_name.c_str(), unique_id.c_str(),
will_Topic, "switch", value_template.c_str(),
payload_on.c_str(), payload_off.c_str(), "", 0,
Gateway_AnnouncementMsg, will_Message, false, subjectMQTTtoBTset,
model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false,
stateClassNone, "off", "on");
} else {
createDiscovery("sensor",
discovery_topic.c_str(), entity_name.c_str(), unique_id.c_str(),
will_Topic, prop.value()["name"], value_template.c_str(),
"", "", prop.value()["unit"],
0, "", "", false, "",
model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false,
stateClassMeasurement);
}
}
}
} else if (p->sensorModel_id > BLEconectable::id::MIN &&
@@ -1001,9 +1016,80 @@ void BTforceScan() {
}
}
# ifdef ESP32
void immediateBTAction(void* pvParameters) {
if (BLEactions.size()) {
// Immediate action; we need to prevent the normal connection action and stop scanning
ProcessLock = true;
NimBLEScan* pScan = NimBLEDevice::getScan();
if (pScan->isScanning()) {
pScan->stop();
}
if (xSemaphoreTake(semaphoreBLEOperation, pdMS_TO_TICKS(5000)) == pdTRUE) {
// swap the vectors so only this device is processed
std::vector<BLEdevice*> dev_swap;
dev_swap.push_back(getDeviceByMac(BLEactions.back().addr));
std::swap(devices, dev_swap);
std::vector<BLEAction> act_swap;
act_swap.push_back(BLEactions.back());
BLEactions.pop_back();
std::swap(BLEactions, act_swap);
// Unlock here to allow the action to be performed
ProcessLock = false;
BLEconnect();
// back to normal
std::swap(devices, dev_swap);
std::swap(BLEactions, act_swap);
// If we stopped the scheduled connect for this action, do the scheduled now
if ((!(scanCount % BLEscanBeforeConnect) || scanCount == 1) && bleConnect)
BLEconnect();
xSemaphoreGive(semaphoreBLEOperation);
} else {
Log.error(F("BLE busy - command not sent" CR));
JsonObject result = getBTJsonObject();
result["id"] = BLEactions.back().addr;
result["success"] = false;
pubBT(result);
BLEactions.pop_back();
ProcessLock = false;
}
}
vTaskDelete(NULL);
}
void startBTActionTask() {
TaskHandle_t th;
xTaskCreatePinnedToCore(
immediateBTAction, /* Function to implement the task */
"imActTask", /* Name of the task */
5120, /* Stack size in bytes */
NULL, /* Task input parameter */
3, /* Priority of the task (set higher than core task) */
&th, /* Task handle. */
1); /* Core where the task should run */
}
# endif
void MQTTtoBTAction(JsonObject& BTdata) {
# ifdef ESP32
BLEAction action;
memset(&action, 0, sizeof(BLEAction));
if (BTdata.containsKey("SBS1")) {
strcpy(action.addr, (const char*)BTdata["mac"]);
action.write = true;
action.value = BTdata["SBS1"].as<std::string>();
action.ttl = 1;
createOrUpdateDevice(action.addr, device_flags_connect,
TheengsDecoder::BLE_ID_NUM::SBS1, 1);
BLEactions.push_back(action);
startBTActionTask();
return;
}
action.ttl = BTdata.containsKey("ttl") ? (uint8_t)BTdata["ttl"] : 1;
action.addr_type = BTdata.containsKey("mac_type") ? BTdata["mac_type"].as<int>() : 0;
action.value_type = BLE_VAL_STRING;
@@ -1050,43 +1136,9 @@ void MQTTtoBTAction(JsonObject& BTdata) {
TheengsDecoder::BLE_ID_NUM::UNKNOWN_MODEL,
action.addr_type);
BLEactions.push_back(action);
if (BTdata.containsKey("immediate") && BTdata["immediate"].as<bool>()) {
// Immediate action; we need to prevent the normal connection action and stop scanning
ProcessLock = true;
NimBLEScan* pScan = NimBLEDevice::getScan();
if (pScan->isScanning()) {
pScan->stop();
}
if (xSemaphoreTake(semaphoreBLEOperation, pdMS_TO_TICKS(5000)) == pdTRUE) {
// swap the vectors so only this device is processed
std::vector<BLEdevice*> dev_swap;
dev_swap.push_back(getDeviceByMac(action.addr));
std::swap(devices, dev_swap);
std::vector<BLEAction> act_swap;
act_swap.push_back(action);
std::swap(BLEactions, act_swap);
// Unlock here to allow the action to be performed
ProcessLock = false;
BLEconnect();
// back to normal
std::swap(devices, dev_swap);
std::swap(BLEactions, act_swap);
// If we stopped the scheduled connect for this action, do the scheduled now
if ((!(scanCount % BLEscanBeforeConnect) || scanCount == 1) && bleConnect)
BLEconnect();
xSemaphoreGive(semaphoreBLEOperation);
} else {
Log.error(F("BLE busy - command not sent" CR));
JsonObject result = getBTJsonObject();
result["id"] = action.addr;
result["success"] = false;
pubBT(result);
}
} else {
BLEactions.push_back(action);
startBTActionTask();
}
# endif
}
+5 -1
View File
@@ -221,7 +221,7 @@ void createDiscovery(const char* sensor_type,
int off_delay,
const char* payload_available, const char* payload_not_avalaible, bool gateway_entity, const char* cmd_topic,
const char* device_name, const char* device_manufacturer, const char* device_model, const char* device_mac, bool retainCmd,
const char* state_class) {
const char* state_class, const char* state_off, const char* state_on) {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject sensor = jsonBuffer.to<JsonObject>();
@@ -282,6 +282,10 @@ void createDiscovery(const char* sensor_type,
sensor["pl_not_avail"] = payload_not_avalaible; //payload_off
if (state_class[0])
sensor["state_class"] = state_class; //add the state class on the sensors ( https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes )
if (state_on != nullptr)
sensor["stat_on"] = state_on;
if (state_off != nullptr)
sensor["stat_off"] = state_off;
if (cmd_topic[0]) {
char command_topic[mqtt_topic_max_size];
+1 -1
View File
@@ -88,7 +88,7 @@ extern void createDiscovery(const char* sensor_type,
int off_delay,
const char* payload_available, const char* payload_not_avalaible, bool gateway_entity, const char* command_topic,
const char* device_name, const char* device_manufacturer, const char* device_model, const char* device_mac, bool retainCmd,
const char* state_class);
const char* state_class, const char* state_off = nullptr, const char* state_on = nullptr);
/**
* @brief Create a message for Discovery Device Trigger. For HA @see https://www.home-assistant.io/integrations/device_trigger.mqtt/
+9 -9
View File
@@ -212,11 +212,11 @@ board_build.ldscript = eagle.flash.1m64.ld ;this frees more space for firmware u
[env:sonoff-rfbridge-direct]
platform = espressif8266@^2
board = esp8285
lib_deps =
lib_deps =
${com-esp.lib_deps}
${libraries.wifimanager8266}
${libraries.esppilight}
build_flags =
build_flags =
${com-esp.build_flags}
'-DZgatewayPilight="Pilight"'
'-DRF_RECEIVER_GPIO=4'
@@ -374,9 +374,9 @@ lib_deps =
${com-esp.lib_deps}
${libraries.wifimanager32}
${libraries.emodbus}
${libraries.gfSunInverter}
${libraries.gfSunInverter}
build_flags =
${com-esp.build_flags}
${com-esp.build_flags}
'-DZgatewayGFSunInverter="GFSunInverter"'
'-DGateway_Name="OpenMQTTGateway_ESP32_GFSunInverter"'
@@ -702,7 +702,7 @@ lib_deps =
${libraries.ble}
${libraries.decoder}
${libraries.irremoteesp}
build_flags =
build_flags =
${com-esp.build_flags}
'-DZgatewayBT="BT"'
'-DZgatewayIR="IR"'
@@ -726,7 +726,7 @@ build_flags =
'-DZgatewayRTL_433="rtl_433"'
'-DGateway_Name="OpenMQTTGateway_rtl_433_ESP"'
'-DvalueAsASubject=true' ; mqtt topic includes model and device
; '-DPUBLISH_UNPARSED=true' ; Publish details of undecoded signals
; '-DPUBLISH_UNPARSED=true' ; Publish details of undecoded signals
; '-DRTL_DEBUG=4' ; enable rtl_433 verbose device decode
[env:esp32dev-multi_receiver]
@@ -810,7 +810,7 @@ build_flags =
'-DZgatewayLORA="LORA"'
'-DZgatewayBT="BT"'
'-DGateway_Name="OpenMQTTGateway_ESP32_BLE_LORA"'
'-DLED_SEND_RECEIVE=21' # T-BEAM board V0.5
# '-DLED_SEND_RECEIVE=14' # T-BEAM board V0.7
# '-DLED_SEND_RECEIVE=4' # T-BEAM board V1.0+
@@ -823,7 +823,7 @@ build_flags =
'-DADC_GPIO=35'
'-DADC_DIVIDER=2'
# Reading battery level every minutes should be more than enought
'-DTimeBetweenReadingADC=60000'
'-DTimeBetweenReadingADC=60000'
[env:heltec-wifi-lora-32-868]
platform = ${com.esp32_platform}
@@ -990,7 +990,7 @@ lib_deps =
${libraries.wifimanager8266}
${libraries.irremoteesp}
${libraries.esp8266_mdns}
build_flags =
build_flags =
${com-esp.build_flags}
'-DZgatewayIR="IR"'
'-DTRIGGER_GPIO=13'