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:
1 parent
a15a1c0851
commit
8d6acbee12
7 files changed
+222
-62
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
Reference in new issue
Block a user