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. `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 ## 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: 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; 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 //ESP32
#endif //zBLEConnect_h #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 //ZgatewayBT
#endif //ESP32 #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 Send and receiving command by MQTT
This gateway enables to: This gateway enables to:
- publish MQTT data to a different topic related to BLE devices rssi signal - 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 flora temperature, moisture, fertility and lux
- publish MQTT data related to mi jia indoor temperature & humidity sensor - publish MQTT data related to mi jia indoor temperature & humidity sensor
Copyright: (c)Florian ROBERT Copyright: (c)Florian ROBERT
This file is part of OpenMQTTGateway. This file is part of OpenMQTTGateway.
OpenMQTTGateway is free software: you can redistribute it and/or modify OpenMQTTGateway is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or 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 * BLEscan used to retrieve BLE advertized data from devices without connection
*/ */
void BLEscan() { void BLEscan() {
@@ -484,7 +484,7 @@ void BLEscan() {
Log.trace(F("Process BLE stack free: %u" CR), uxTaskGetStackHighWaterMark(xProcBLETaskHandle)); Log.trace(F("Process BLE stack free: %u" CR), uxTaskGetStackHighWaterMark(xProcBLETaskHandle));
} }
/** /**
* Connect to BLE devices and initiate the callbacks with a service/characteristic request * Connect to BLE devices and initiate the callbacks with a service/characteristic request
*/ */
void BLEconnect() { void BLEconnect() {
@@ -513,6 +513,9 @@ void BLEconnect() {
XMWSDJ04MMC_connect BLEclient(addr); XMWSDJ04MMC_connect BLEclient(addr);
BLEclient.processActions(BLEactions); BLEclient.processActions(BLEactions);
BLEclient.publishData(); BLEclient.publishData();
} else if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::SBS1) {
SBS1_connect BLEclient(addr);
BLEclient.processActions(BLEactions);
} else { } else {
GENERIC_connect BLEclient(addr); GENERIC_connect BLEclient(addr);
if (BLEclient.processActions(BLEactions)) { if (BLEclient.processActions(BLEactions)) {
@@ -874,13 +877,25 @@ void launchBTDiscovery() {
# else # else
String value_template = "{{ value_json." + String(prop.key().c_str()) + " | is_defined }}"; String value_template = "{{ value_json." + String(prop.key().c_str()) + " | is_defined }}";
# endif # endif
createDiscovery("sensor", if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::SBS1 && !strcmp(prop.key().c_str(), "state")) {
discovery_topic.c_str(), entity_name.c_str(), unique_id.c_str(), String payload_on = "{\"SBS1\":\"on\",\"mac\":\"" + String(p->macAdr) + "\"}";
will_Topic, prop.value()["name"], value_template.c_str(), String payload_off = "{\"SBS1\":\"off\",\"mac\":\"" + String(p->macAdr) + "\"}";
"", "", prop.value()["unit"], createDiscovery("switch", //set Type
0, "", "", false, "", discovery_topic.c_str(), entity_name.c_str(), unique_id.c_str(),
model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false, will_Topic, "switch", value_template.c_str(),
stateClassMeasurement); 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 && } 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) { void MQTTtoBTAction(JsonObject& BTdata) {
# ifdef ESP32 # ifdef ESP32
BLEAction action; 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.ttl = BTdata.containsKey("ttl") ? (uint8_t)BTdata["ttl"] : 1;
action.addr_type = BTdata.containsKey("mac_type") ? BTdata["mac_type"].as<int>() : 0; action.addr_type = BTdata.containsKey("mac_type") ? BTdata["mac_type"].as<int>() : 0;
action.value_type = BLE_VAL_STRING; action.value_type = BLE_VAL_STRING;
@@ -1050,43 +1136,9 @@ void MQTTtoBTAction(JsonObject& BTdata) {
TheengsDecoder::BLE_ID_NUM::UNKNOWN_MODEL, TheengsDecoder::BLE_ID_NUM::UNKNOWN_MODEL,
action.addr_type); action.addr_type);
BLEactions.push_back(action);
if (BTdata.containsKey("immediate") && BTdata["immediate"].as<bool>()) { if (BTdata.containsKey("immediate") && BTdata["immediate"].as<bool>()) {
// Immediate action; we need to prevent the normal connection action and stop scanning startBTActionTask();
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);
} }
# endif # endif
} }
+5 -1
View File
@@ -221,7 +221,7 @@ void createDiscovery(const char* sensor_type,
int off_delay, int off_delay,
const char* payload_available, const char* payload_not_avalaible, bool gateway_entity, const char* cmd_topic, 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* 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; StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject sensor = jsonBuffer.to<JsonObject>(); JsonObject sensor = jsonBuffer.to<JsonObject>();
@@ -282,6 +282,10 @@ void createDiscovery(const char* sensor_type,
sensor["pl_not_avail"] = payload_not_avalaible; //payload_off sensor["pl_not_avail"] = payload_not_avalaible; //payload_off
if (state_class[0]) 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 ) 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]) { if (cmd_topic[0]) {
char command_topic[mqtt_topic_max_size]; 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, int off_delay,
const char* payload_available, const char* payload_not_avalaible, bool gateway_entity, const char* command_topic, 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* 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/ * @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] [env:sonoff-rfbridge-direct]
platform = espressif8266@^2 platform = espressif8266@^2
board = esp8285 board = esp8285
lib_deps = lib_deps =
${com-esp.lib_deps} ${com-esp.lib_deps}
${libraries.wifimanager8266} ${libraries.wifimanager8266}
${libraries.esppilight} ${libraries.esppilight}
build_flags = build_flags =
${com-esp.build_flags} ${com-esp.build_flags}
'-DZgatewayPilight="Pilight"' '-DZgatewayPilight="Pilight"'
'-DRF_RECEIVER_GPIO=4' '-DRF_RECEIVER_GPIO=4'
@@ -374,9 +374,9 @@ lib_deps =
${com-esp.lib_deps} ${com-esp.lib_deps}
${libraries.wifimanager32} ${libraries.wifimanager32}
${libraries.emodbus} ${libraries.emodbus}
${libraries.gfSunInverter} ${libraries.gfSunInverter}
build_flags = build_flags =
${com-esp.build_flags} ${com-esp.build_flags}
'-DZgatewayGFSunInverter="GFSunInverter"' '-DZgatewayGFSunInverter="GFSunInverter"'
'-DGateway_Name="OpenMQTTGateway_ESP32_GFSunInverter"' '-DGateway_Name="OpenMQTTGateway_ESP32_GFSunInverter"'
@@ -702,7 +702,7 @@ lib_deps =
${libraries.ble} ${libraries.ble}
${libraries.decoder} ${libraries.decoder}
${libraries.irremoteesp} ${libraries.irremoteesp}
build_flags = build_flags =
${com-esp.build_flags} ${com-esp.build_flags}
'-DZgatewayBT="BT"' '-DZgatewayBT="BT"'
'-DZgatewayIR="IR"' '-DZgatewayIR="IR"'
@@ -726,7 +726,7 @@ build_flags =
'-DZgatewayRTL_433="rtl_433"' '-DZgatewayRTL_433="rtl_433"'
'-DGateway_Name="OpenMQTTGateway_rtl_433_ESP"' '-DGateway_Name="OpenMQTTGateway_rtl_433_ESP"'
'-DvalueAsASubject=true' ; mqtt topic includes model and device '-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 ; '-DRTL_DEBUG=4' ; enable rtl_433 verbose device decode
[env:esp32dev-multi_receiver] [env:esp32dev-multi_receiver]
@@ -810,7 +810,7 @@ build_flags =
'-DZgatewayLORA="LORA"' '-DZgatewayLORA="LORA"'
'-DZgatewayBT="BT"' '-DZgatewayBT="BT"'
'-DGateway_Name="OpenMQTTGateway_ESP32_BLE_LORA"' '-DGateway_Name="OpenMQTTGateway_ESP32_BLE_LORA"'
'-DLED_SEND_RECEIVE=21' # T-BEAM board V0.5 '-DLED_SEND_RECEIVE=21' # T-BEAM board V0.5
# '-DLED_SEND_RECEIVE=14' # T-BEAM board V0.7 # '-DLED_SEND_RECEIVE=14' # T-BEAM board V0.7
# '-DLED_SEND_RECEIVE=4' # T-BEAM board V1.0+ # '-DLED_SEND_RECEIVE=4' # T-BEAM board V1.0+
@@ -823,7 +823,7 @@ build_flags =
'-DADC_GPIO=35' '-DADC_GPIO=35'
'-DADC_DIVIDER=2' '-DADC_DIVIDER=2'
# Reading battery level every minutes should be more than enought # Reading battery level every minutes should be more than enought
'-DTimeBetweenReadingADC=60000' '-DTimeBetweenReadingADC=60000'
[env:heltec-wifi-lora-32-868] [env:heltec-wifi-lora-32-868]
platform = ${com.esp32_platform} platform = ${com.esp32_platform}
@@ -990,7 +990,7 @@ lib_deps =
${libraries.wifimanager8266} ${libraries.wifimanager8266}
${libraries.irremoteesp} ${libraries.irremoteesp}
${libraries.esp8266_mdns} ${libraries.esp8266_mdns}
build_flags = build_flags =
${com-esp.build_flags} ${com-esp.build_flags}
'-DZgatewayIR="IR"' '-DZgatewayIR="IR"'
'-DTRIGGER_GPIO=13' '-DTRIGGER_GPIO=13'