Skip to content

Commit

Permalink
bleOnDemand implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron Ibarra V committed Jul 26, 2021
1 parent fb93956 commit 77e4ae9
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 8 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ The following MQTT topics are subscribed to:
| ----- | ----------- | ------ |
| am43/<device>/set | Set the blind position | 'OPEN', 'STOP' or 'CLOSE' |
| am43/<device>/set_position | Set the blind % position | between 0 and 100. |
| am43/<device>/status | Get device status on demand (position/light/battery) | Ignored. |
| am43/enable | Enable or disable BLE connections | 'off' or 'on'.
| am43/restart | Reboot this service | Ignored.
| am43/cmnd/# | Only on bleOnDemand mode. This topic process commands with on demand connections. ex. am43/cmnd/<device>/set ; am32/cmnd/<device>/set_position | Depends on every command

<device> is the bluetooth mac address of the device, eg 02:69:32:f0:c5:1d

Expand All @@ -62,6 +64,11 @@ In the file tab *config.h*, configure your Wifi credentials and your MQTT server
details. If your AM43 devices are not using the default pin (8888) also set it
there.

### Components needed

- ESP-32 --> v1.0.6 (https://github.com/espressif/arduino-esp32)
- NimBLE-Arduino --> v1.2.0 (https://github.com/h2zero/NimBLE-Arduino)

### Installation with NimBLE

As of Version 0.5.0, the library uses the [NimBLE bluetooth stack](https://github.com/h2zero/NimBLE-Arduino/),
Expand Down Expand Up @@ -154,7 +161,7 @@ Once you have your device ready, you can monitor and control it using an MQTT
client. For example, using mosquitto_sub, you can watch activity with:

```
$ mosquitto_sub -h <mqtt_server> -v -t am43/#
$ mosquitto_sub -h <mqtt_server> -p <mqtt_port> -v -t am43/#
am43/LWT Online
am43/026932f2c41d/available online
am43/026932f2c41d/position 0
Expand All @@ -169,13 +176,13 @@ am43/024d45f05b2e/light 68
It's trivial to then control the shades similarly:

```
$ mosquitto_sub -h <mqtt_server> -t am43/026932f2c41d/set -m OPEN
$ mosquitto_pub -h <mqtt_server> -p <mqtt_port> -t am43/026932f2c41d/set -m OPEN
```

You can also control all in unison:

```
$ mosquitto_sub -h <mqtt_server> -t am43/all/set -m CLOSE
$ mosquitto_pub -h <mqtt_server> -p <mqtt_port> -t am43/all/set -m CLOSE
```

## Multiple devices
Expand Down Expand Up @@ -323,7 +330,7 @@ pio run -t monitor

- Consider more functionality such as device configuration.
- Allow buttons on the ESP32 for control?
- On-demand BLE connect to save AM43 device battery.
- <s>On-demand BLE connect to save AM43 device battery.</s>

## Copyright

Expand Down
81 changes: 77 additions & 4 deletions examples/MQTTBlinds/MQTTBlinds.ino
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,21 @@ void mqtt_callback(char* top, byte* pay, unsigned int length);

unsigned long lastScan = 0;
boolean scanning = false;

#ifdef AM43_ONDEMAND
boolean bleEnabled = false; // Controlled via MQTT.
boolean bleOnDemand = true;
#else
boolean bleEnabled = true; // Controlled via MQTT.
boolean bleOnDemand = false;
#endif

const uint16_t bleOnDemandTimeout = 60000;
unsigned long onDemandTime = millis();
boolean bleOnDemandCommandQueued = false;
String bleOnDemandAddress;
String bleOnDemandCommand;
String bleOnDemandCommandValue;
BLEScan* pBLEScan;

const char discTopicTmpl[] PROGMEM = "homeassistant/cover/%s/config";
Expand Down Expand Up @@ -172,6 +186,7 @@ class MyAM43Callbacks: public AM43Callbacks {
this->mqtt->publish(topic("available").c_str(), "online", true);
this->mqtt->subscribe(topic("set").c_str());
this->mqtt->subscribe(topic("set_position").c_str());
this->mqtt->subscribe(topic("status").c_str());

#ifdef AM43_ENABLE_MQTT_DISCOVERY
char discTopic[128];
Expand Down Expand Up @@ -235,6 +250,12 @@ void mqtt_callback(char* top, byte* pay, unsigned int length) {
i2 = topic.indexOf('/', i1+1);
String address = topic.substring(i1+1, i2);
String command = topic.substring(i2+1);
if (address == "cmnd") {
i3 = topic.indexOf('/', i2+1);
address = topic.substring(i2+1, i3);
command = topic.substring(i3+1);
}
boolean commandOnDemand = false;
Serial.printf("Addr: %s Cmd: %s\r\n", address.c_str(), command.c_str());
payload.toLowerCase();

Expand All @@ -251,6 +272,9 @@ void mqtt_callback(char* top, byte* pay, unsigned int length) {
Serial.println("Disabling BLE Clients");
bleEnabled = false;
pubSubClient.publish(topPrefix("/enabled").c_str(), "OFF", true);
#ifdef AM43_ONDEMAND
bleOnDemand = true;
#endif
for (auto const& c : cls) {
c.second->client->disconnectFromServer();
}
Expand All @@ -259,6 +283,9 @@ void mqtt_callback(char* top, byte* pay, unsigned int length) {
bleEnabled = true;
pubSubClient.publish(topPrefix("/enabled").c_str(), "ON", true);
lastScan = 0;
#ifdef AM43_ONDEMAND
bleOnDemand = false;
#endif
}
}

Expand All @@ -272,8 +299,34 @@ void mqtt_callback(char* top, byte* pay, unsigned int length) {
}
if (command == "set_position")
cl->setPosition(payload.toInt());
if (command == "status") {
cl->sendGetBatteryRequest();
cl->sendGetPositionRequest();
cl->sendGetLightRequest();
}
}
}

if (command == "status" || command == "set" || command == "set_position" || command == "status") commandOnDemand = true;

if (commandOnDemand && bleOnDemand && millis() - onDemandTime > bleOnDemandTimeout) {
onDemandTime = millis();
onDemand_BLEScan();
if (command != "status") {
bleOnDemandCommandQueued = true;
bleOnDemandAddress = address;
bleOnDemandCommand = command;
bleOnDemandCommandValue = payload;
}
}

}

void onDemand_BLEScan() {
Serial.println("Starting an OnDemand BLE Scan...");
scanning = true;
lastScan = 0;
pBLEScan->start(3, bleScanComplete, false);
}

std::vector<BLEAddress> allowList;
Expand Down Expand Up @@ -321,7 +374,7 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice->haveServiceUUID() && advertisedDevice->isAdvertisingService(serviceUUID)) {
if (!bleEnabled) {
if (!bleEnabled && !bleOnDemand) {
Serial.printf("BLE connections disabled, ignoring device\r\n");
return;
}
Expand Down Expand Up @@ -434,6 +487,8 @@ void reconnect_mqtt() {
pubSubClient.subscribe(topPrefix("/enable").c_str());
pubSubClient.subscribe(topPrefix("/all/set").c_str());
pubSubClient.subscribe(topPrefix("/all/set_position").c_str());
pubSubClient.subscribe(topPrefix("/all/status").c_str());
if (bleOnDemand) pubSubClient.subscribe(topPrefix("/cmnd/#").c_str());
pubSubClient.loop();

char discTopic[128];
Expand All @@ -442,7 +497,9 @@ void reconnect_mqtt() {
sprintf(discTopic, discSwitchTopic, MQTT_TOPIC_PREFIX);
sprintf(discPayload, discSwitchPayload, MQTT_TOPIC_PREFIX, MQTT_TOPIC_PREFIX, MQTT_TOPIC_PREFIX);
pubSubClient.publish(discTopic, discPayload, true);
pubSubClient.publish(topPrefix("/enabled").c_str(), "ON", true);
String msgEnabled = "ON";
if (!bleEnabled) msgEnabled = "OFF";
pubSubClient.publish(topPrefix("/enabled").c_str(), msgEnabled.c_str(), true);
} else {
Serial.print("failed, rc=");
Serial.print(pubSubClient.state());
Expand Down Expand Up @@ -537,11 +594,25 @@ void loop() {
for (auto const &c : cls) {
if (c.second->client->m_DoConnect && !scanning) {
c.second->client->connectToServer(notifyCallback);
if (bleOnDemandCommandQueued) c.second->client->setCommandQueued(true);
break; // Connect takes some time, so break out to allow other processing.
}
if (c.second->client->m_Connected) {
c.second->client->update();
c.second->handle();
if (c.second->client->m_CommandQueued && (bleOnDemandAddress == "all" || bleOnDemandAddress == c.second->mqttName)) {
c.second->client->setCommandQueued(false);
if (bleOnDemandCommand == "set") {
if (bleOnDemandCommandValue == "open") c.second->client->open();
if (bleOnDemandCommandValue == "close") c.second->client->close();
if (bleOnDemandCommandValue == "stop") c.second->client->stop();
}
if (bleOnDemandCommand == "set_position") c.second->client->setPosition(bleOnDemandCommandValue.toInt());
}
if (bleOnDemand && millis() - onDemandTime > bleOnDemandTimeout) {
bleOnDemandCommandQueued = false;
c.second->client->disconnectFromServer();
}
}
if (c.second->client->m_Disconnected) removeList.push_back(c.first);
}
Expand All @@ -565,8 +636,10 @@ void loop() {
}
// Start a new scan every 60s.
if (millis() - lastScan > 60000 && !otaUpdating && !scanning) {
scanning = true;
pBLEScan->start(10, bleScanComplete, false);
if (bleEnabled) {
scanning = true;
pBLEScan->start(10, bleScanComplete, false);
}
lastScan = millis();
Serial.printf("Up for %ds\r\n", millis()/1000);
}
Expand Down
13 changes: 13 additions & 0 deletions examples/MQTTBlinds/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@
// PIN for the AM43 device (printed on it, default is 8888)
#define AM43_PIN 8888

// Enable this to use OnDemand feature :
// This allows to maintain disconnected from BLE devices and,
// Receive command from MQTT and connect and send commands to AM43
// OnDemand. The devices commands needs to be sent to AM43/cmnd/MAC-ADDRESS/command
//
// Note: Commands can have a delay of 5-10s.
// Config change recommended! : To improve status updates,
// change AM43_UPDATE_INTERVAL to 5000 in AM43Clien.h file
// ex. #define AM43_UPDATE_INTERVAL 5000
//
// To enable, uncomment the next line:
//#define AM43_ONDEMAND

// Comma separated list of MAC addresses to allow for control. Useful if you
// have multiple ESP controllers. Leave empty to allow all devices.
//
Expand Down
5 changes: 5 additions & 0 deletions src/AM43Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ AM43Client::AM43Client(BLEAdvertisedDevice *d, uint16_t pin) {
this->m_BatteryPercent = 0xff;
this->m_ClientCallbacks = nullptr;
this->m_CurrentQuery = 1;
this->m_CommandQueued = false;
}

void AM43Client::onConnect(BLEClient* pclient) {
Expand Down Expand Up @@ -192,6 +193,10 @@ void AM43Client::setPosition(uint8_t pos) {
this->sendCommand(AM43_COMMAND_SET_POSITION, data);
}

void AM43Client::setCommandQueued(boolean status) {
this->m_CommandQueued = status;
}

void AM43Client::update() {
if (millis() - this->m_LastUpdate > AM43_UPDATE_INTERVAL) {
if (!this->m_LoggedIn) {
Expand Down
3 changes: 3 additions & 0 deletions src/AM43Client.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class AM43Client : public BLEClientCallbacks {
boolean m_DoConnect;
// We're logged in (correct pin)
boolean m_LoggedIn;
// OnDemand command flag
bollean m_CommandQueued;

// Latest battery level (percent)
unsigned char m_BatteryPercent;
Expand Down Expand Up @@ -80,6 +82,7 @@ class AM43Client : public BLEClientCallbacks {
void stop();
void close();
void setPosition(uint8_t);
void setCommandQueued(boolean);

protected:
void sendPin();
Expand Down

0 comments on commit 77e4ae9

Please sign in to comment.