A library and example sketch that allows you to control AM43 style blind controllers using an ESP32 device's built-in Bluetooth radio.
Included is an example sketch to perform control and monitoring via MQTT, including auto-discovery for Home Assistant.
These blind and rollershade controllers are sold under various names, including Zemismart, A-OK and all use the "Blind Engine" app.
Feel free to send PRs.
Looking for native ESPHome support? See this repo
This sketch will scan for and auto-connect to any AM43 devices in range, then provide MQTT topics to control and get status from them.
For Home Assisant, auto-discovery is supported so connected AM43 devices will show up as entities. See below for simple setup.
Note that the Arduino ESP32 integration only supports connecting to a maximum of 3 bluetooth devices. See below if you have more.
The following MQTT topis are published to:
Topic | Description | Values |
---|---|---|
am43/<device>/available | Connection status of the blind controller | Either 'offline' or 'online' |
am43/<device>/position | The current blind position | between 0 and 100 |
am43/<device>/battery | The current battery level | between 0 and 100 |
am43/<device>/light | The current light level | between 0 and 100 |
am43/enabled | Whether the BLE client is enabled | Either "on" or "off" |
am43/LWT | MQTT connection status | Either 'Online' or 'Offline' |
The following MQTT topics are subscribed to:
Topic | Description | Values |
---|---|---|
am43/<device>/set | Set the blind position (would be deprecated in future release) | 'OPEN', 'STOP' or 'CLOSE' |
am43/<device>/set_position | Set the blind position or % position | 'OPEN', 'UP', 'STOP', 'CLOSE', 'DOWN' or a number between 0 and 100. |
am43/<device>/status | Get device status on demand (position/light/battery) | Ignored. |
am43/enable | Enable = BLE connections AlwaysOn mode, Disable = BLE OnDemand Connections mode | '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 ; am43/cmnd/<device>/set_position | Depends on every command |
<device> is the bluetooth mac address of the device, eg 02:69:32:f0:c5:1d
If you enable AM43_USE_NAME_FOR_TOPIC in config.h, then the device name configured is used in the topic instead of the mac.
For the position set commands, you can use name 'all' to change all devices.
Download this archive and unzip it to your Arduino installation's libraries folder, it should extract as its own sub-folder. Then (re)start the IDE.
Open the example Sketch under File -> Examples -> AM43Client -> MQTTBlinds
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.
This mode works with on demand connections to AM43 devices, so when receives a command, it's start on demand connection to device, send the command requested and wait 60 seconds before disconnect from devices.
To enable this mode, edit the config.h file and uncomment the line #define AM43_ONDEMAND
Is strongly recommended, to reduce the update interval from 5000 to 20000, to have enough
time to get all devices updates (battery/light/position). To change this, edit the
AM43Client.h file and modify line from #define AM43_UPDATE_INTERVAL 30000
to #define AM43_UPDATE_INTERVAL 5000
.
As of Version 0.5.0, the library uses the NimBLE bluetooth stack, rather than the legacy Arduino stack. This is signficantly smaller in both flash and RAM usage, so should increase stability. You can download the NimBLE library directly within the Arduino Library Manager (Sketch->Include Library->Manage Libraries).
It's recommended to use the NimBLE library, but if you are having issues with this, you can use the legacy stack (please also raise a bug).
To have the AM43 library use the legacy BLE stack, edit the file AM43Client.h in your
installation and comment out the line #define USE_NIMBLE
near the top.
The sketch takes up a lot of space on flash thanks to the use of Wifi and BLE - you will need to increase the available space by changing the board options in the Arduino IDE. Once you have selected your ESP32 board in Tools, select Tools -> Partition Scheme -> Minimal SPIFFS to enable the larger program space.
Whilst developing this, I found bugs in the ESP32 Arduino BLE libraries which cause significant instability issues. A patch will been submitted to the BLE maintainers, though if it's not yet in your release, you will need to make the following changes, which result in a massive stability gain:
Find BLEClient.cpp in your installation, and make the following changes.
// Search for the following block, around line 180 and add the line.
case ESP_GATTC_DISCONNECT_EVT: {
if (evtParam->disconnect.conn_id != getConnId()) break; // <- ADD THIS LINE
// If we receive a disconnect event, set the class flag that indicates that we are
// no longer connected.
m_isConnected = false;
// Also two changes around line 238.
case ESP_GATTC_CONNECT_EVT: {
if (evtParam->connect.conn_id != getConnId()) break; // <- ADD THIS LINE
BLEDevice::updatePeerDevice(this, true, m_appId);
// ^^^^^^^ CHANGE THIS PARAMETER.
Next, find BLEScan.cpp, and add the following on line ~28 in the Constructor:
m_scan_params.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE;
Finally, you need to find the file esp32-hal-bt.c and make the following changes:
// Change the mode to BLE only on the following line:
bool btStart(){
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
cfg.mode = ESP_BT_MODE_BLE; // <- ADD THIS LINE
// Then around 10 lines below this, replace these lines:
// if (esp_bt_controller_enable(BT_MODE)) {
// log_e("BT Enable failed");
// return false;
// }
// with:
auto err_p = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (err_p) {
log_e("BT Enable failed err=%d", err_p);
return false;
}
Note that only one BLE client can connect to the AM43 at a time. So you cannot have multiple ESP controllers or have the device app (Blind Engine) running at the same time.
You should perform initial setup of your AM43 devices with the native app before running this gateway. This includes setting the device name and motor direction.
Lots of information is printed over the serial console. Connect to your ESP32 device at 115200 baud and there should be plenty of chatter.
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> -p <mqtt_port> -v -t am43/#
am43/LWT Online
am43/026932f2c41d/available online
am43/026932f2c41d/position 0
am43/026932f2c41d/battery 70
am43/026932f2c41d/light 49
am43/024d45f05b2e/available online
am43/024d45f05b2e/battery 100
am43/024d45f05b2e/position 0
am43/024d45f05b2e/light 68
It's trivial to then control the shades similarly:
$ mosquitto_pub -h <mqtt_server> -p <mqtt_port> -t am43/026932f2c41d/set -m OPEN
You can also control all in unison:
$ mosquitto_pub -h <mqtt_server> -p <mqtt_port> -t am43/all/set -m CLOSE
If you have up to three AM43 devices connecting to the one ESP32, then it should just work out of the box.
However if you have more devices, or just want to limit which AM43's are controlled by a given ESP32, then you'll need to set up as follows.
This might be for a setup where you have AM43s in different parts of the house and you'd like to ensure each ESP32 only controls specific motor(s) such as the closest.
For each ESP32, you should edit config.h and add the MAC address of the desired AM43(s) to DEVICE_ALLOWLIST. Separate each address by a comma. You can find the addresses either in the Arduino serial console or the Blind Engine app. Then that ESP will not connect to any other AM43.
Arduino BLE can only connect to at most three bluetooth devices (unless you custom compile it). If you have more devices you will need to have additional ESP32 proxies with this code, each setup with a separate allow list as above.
There is now a repo which allows you to integrate AM43 support directly into ESPHome without downloading or installing anything beyond your standard ESPHome installation.
See https://github.com/buxtronix/esphome-am43
Ref: Home Assistant Auto Discovery
If you have enabled auto-discovery in config.h as well as in Home Assistant, then once your blinds are setup and the sketch has connected, you will see them appear in the entity list under Configuration->Entities either by the device name or mac address.
It's easy to add them to the Lovelace dashboard - create a new Entities Card and find the entities matching your device names (this is the name configured in the Blinds Engine app), or the MAC address. There will be three entities per AM43 device - one cover and two sensors for the battery and light levels.
There will also be a switch entity created, used to enable and disable the BLE connections. This can be useful to save battery power by only establishing the BLE connection when moving the cover. Note that it may take up to a minute to establish the connection so automations should add a delay between enabling the switch and sending a command.
Example automation with only enabling BLE when changing the cover:
id: 'Sunrise blinds'
trigger:
- event: sunrise
platform: sun
action:
- entity_id: switch.am43_ble
service: switch.turn_on
- delay: 00:01
- entity_id: cover.bedroom
service: cover.open_cover
- delay: 00:02
- entity_id: switch.am43_ble
service: switch.turn_off
Check the config.h file for other related options.
The MQTT topics are set to integrate natively with Home Assistant. Once both are talking to the MQTT server, add the following configuration for each blind:
cover:
- platform: mqtt
name: "Bedroom right"
device_class: "shade"
command_topic: "am43/026932f2c41d/set"
position_topic: "am43/026932f2c41d/position"
set_position_topic: "am43/026932f2c41d/set_position"
availability_topic: "am43/026932f2c41d/available"
# Devices dont always report 0, open might be 0, 1 or 2.
position_open: 2
# Devices dont always report 100, closed might be 99 or 100.
position_closed: 99
sensor:
- platform: mqtt
name: "Bedroom right blind battery"
availability_topic: "am43/026932f2c41d/available"
state_topic: "am43/026932f2c41d/battery"
unit_of_measurement: "%"
device_class: battery
- platform: mqtt
name: "Bedroom right blind light"
state_topic: "am43/026932f2c41d/light"
availability_topic: "am43/026932f2c41d/available"
unit_of_measurement: "%"
device_class: illuminance
switch:
- platform: mqtt
name: "Bedroom BLE enable"
command_topic: "am43/enable"
state_topic: "am43/enabled"
availability_topic: "am43/LWT"
icon: "mdi:bluetooth"
Thing mqtt:topic:RollersController "Rollers Controller" (mqtt:broker:MQTTBroker) @ "am43-esp32-controller" [ availabilityTopic="am43/LWT", payloadNotAvailable="Offline",payloadAvailable="Online"] {
Channels:
Type string : reachable "Reachable" [ stateTopic="am43/LWT", transformationPatternOut="MAP:reachable.map" ]
Type switch : allwaysOn "Allways ON mode" [
stateTopic="am43/enabled",
commandTopic="am43/enable",
ON="ON",
OFF="OFF"
]
Type string : roller1Reachable "roller1 Reachable" [ stateTopic="am43/<device-mac>/available", transformationPatternOut="MAP:reachable.map"]
Type rollershutter : roller1Control "roller1 Control" [
stateTopic="am43/<device-mac>/position",
commandTopic="am43/cmnd/<device-mac>/set_position",
UP="OPEN",
DOWN="CLOSE",
STOP="STOP"
]
Type number : roller1Battery "roller1 Battery" [ stateTopic="am43/<device-mac>/battery"]
Type number : roller1Light "roller1 Light Sensor" [ stateTopic="am43/<device-mac>/light"]
Type string : roller2Reachable "roller2 Reachable" [ stateTopic="am43/<device-mac>/available", transformationPatternOut="MAP:reachable.map"]
Type rollershutter : roller2Control "roller2 Control" [
stateTopic="am43/<device-mac>/position",
commandTopic="am43/cmnd/<device-mac>/set_position",
UP="OPEN",
DOWN="CLOSE",
STOP="STOP"
]
Type number : roller2Battery "roller2 Battery" [ stateTopic="am43/<device-mac>/battery"]
Type number : roller2Light "roller2 Light Sensor" [ stateTopic="am43/<device-mac>/light"]
}
Group Equipment_Rollers "Rollers" <blinds> (Location_Group) ["Blinds"]
String ControllerState "Controller State : [%s]" <network> (Equipment_Rollers) ["Status"] {channel="mqtt:topic:RollersController:reachable"}
Switch ControllerBLEMode "AllwaysOn Mode" <switch> (Equipment_Rollers) [ "Switch" ] {channel="mqtt:topic:RollersController:allwaysOn"}
String roller1State "roller1 State : [%s]" <network> (Equipment_Rollers) ["Status"] {channel="mqtt:topic:RollersController:roller1Reachable"}
Rollershutter roller1Position "roller1 Position [%d %%]" <blinds> (Equipment_Rollers) [ "Setpoint" ] {channel="mqtt:topic:RollersController:roller1Control"}
Number roller1Battery "roller1 Battery Level [%d %%]" <battery> (Equipment_Rollers) ["Measurement", "Energy"] {channel="mqtt:topic:RollersController:roller1Battery"}
Number roller1LightLevel "roller1 Light Level [%d %%]" <sun> (Equipment_Rollers) ["Measurement", "Light"] {channel="mqtt:topic:RollersController:roller1Light"}
String roller2State "roller2 State : [%s]" <network> (Equipment_Rollers) ["Status"] {channel="mqtt:topic:RollersController:roller2Reachable"}
Rollershutter roller2Position "roller2 Position [%d %%]" <blinds> (Equipment_Rollers) [ "Setpoint" ] {channel="mqtt:topic:RollersController:roller2Control"}
Number roller2Battery "roller2 Battery Level [%d %%]" <battery> (Equipment_Rollers) ["Measurement", "Energy"] {channel="mqtt:topic:RollersController:roller2Battery"}
Number roller2LightLevel "roller2 Light Level [%d %%]" <sun> (Equipment_Rollers) ["Measurement", "Light"] {channel="mqtt:topic:RollersController:roller2Light"}
reachable.map:
Online=ON
Offline=OFF
online=ON
offline=OFF
Building with PlatformIO is simple, it will manage dependencies automatically.
Copy examples/MQTTBlinds/MQTTBlinds.ino and config.h to the src/ directory.
Edit config.h
Compile and upload via USB
pio run -t upload
Optionally monitor the serial port
pio run -t monitor
Copyright 2020-2021, Ben Buxton. Licenced under the MIT licence, see LICENSE.