Howdy ya’ll!
I have a solution that uses esphome (on an ESP32) that implements the Bluetooth interactions for local control. This is my short-term solution until official support is added to the Bluetooth proxy. I discovered that the tilt uses the same Bluetooth protocol as the curtain bot, at least for the actions. I have not spent the time to reverse engineer the reporting packet fully so it can only control and report position but does not have battery or light level. That said since I define the close actions as hard coded 0% (0x00) and open as 50% (0x32) HA icons all work as expected… for me at least You can also use automation or manually set the position to anything you would like. I have found that the tilt doesn’t always respond to the first attempt so my automation just sets the same position twice with a 500ms delay and it all seems to reliably open/close as expected. Finally, I prefer that closed is “closed down” but if you prefer “closed up” replace 0x00 with 0x64 in the yaml.
To use this you should only have to change the values in “substitutions”. To get the MAC just use the switchbot app, hit the gear on the tilt and then “device info”. Also, each ESP32 can currently only support 3 Bluetooth connections so you will need one for each set of 3 you might have.
If you are new to the ESP32 (for example, Amazon.com) the best way to deploy is to install the HA add-on ESPHome. For the first setup plug it into your computer, then click add new device in ESPHome, select “plugged into this computer”, you should get a pop-up to connect a “serial port”, click accept while holding the “boot” button on the ESP home. It will have a connecting progress indicator, continue to hold “boot” until it changes to “preparing”. After its added click edit, paste the block below, change it for your preferences then click install. Finally, go to your devices in HA and it should eventually show up as a new device, accept and profit!
substitutions:
name: master-bedroom-bluetooth-1
switchbot_tilt_1_mac: F6:E4:28:70:A1:69
switchbot_tilt_1_name: switchbot_tilt_master_bedroom_rear
switchbot_tilt_1_display_name: Switchbot Tilt Master Bedroom (rear)
switchbot_tilt_2_mac: FD:55:29:90:95:EF
switchbot_tilt_2_name: switchbot_tilt_master_bedroom_right
switchbot_tilt_2_display_name: Switchbot Tilt Master Bedroom (right)
switchbot_tilt_3_mac: F4:73:1C:AD:ED:1F
switchbot_tilt_3_name: switchbot_tilt_master_bedroom_left
switchbot_tilt_3_display_name: Switchbot Tilt Master Bedroom (left)
esphome:
name: ${name}
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
ota:
password: "7aac28502f684083ac3c26f0e180aa9d"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "${name}"
password: "F3R73g0hTDbe"
captive_portal:
esp32_ble_tracker:
scan_parameters:
interval: 50ms
window: 50ms
active: true
continuous: true
on_ble_advertise:
- mac_address: ${switchbot_tilt_1_mac}
then:
- lambda: |-
for (auto data : x.get_manufacturer_datas()) {
if (data.uuid.to_string() == "0x0969" && data.data.size() == 10) {
uint8_t position = data.data[8] & 0b01111111;
float position_pct = float(position) / 100.0;
if (id(${switchbot_tilt_1_name}_cover).position != position_pct) {
id(${switchbot_tilt_1_name}_cover).position = position_pct;
id(${switchbot_tilt_1_name}_cover).publish_state();
}
}
}
- mac_address: ${switchbot_tilt_2_mac}
then:
- lambda: |-
for (auto data : x.get_manufacturer_datas()) {
if (data.uuid.to_string() == "0x0969" && data.data.size() == 10) {
uint8_t position = data.data[8] & 0b01111111;
float position_pct = float(position) / 100.0;
if (id(${switchbot_tilt_2_name}_cover).position != position_pct) {
id(${switchbot_tilt_2_name}_cover).position = position_pct;
id(${switchbot_tilt_2_name}_cover).publish_state();
}
}
}
- mac_address: ${switchbot_tilt_3_mac}
then:
- lambda: |-
for (auto data : x.get_manufacturer_datas()) {
if (data.uuid.to_string() == "0x0969" && data.data.size() == 10) {
uint8_t position = data.data[8] & 0b01111111;
float position_pct = float(position) / 100.0;
if (id(${switchbot_tilt_3_name}_cover).position != position_pct) {
id(${switchbot_tilt_3_name}_cover).position = position_pct;
id(${switchbot_tilt_3_name}_cover).publish_state();
}
}
}
ble_client:
- mac_address: ${switchbot_tilt_1_mac}
id: ${switchbot_tilt_1_name}
- mac_address: ${switchbot_tilt_2_mac}
id: ${switchbot_tilt_2_name}
- mac_address: ${switchbot_tilt_3_mac}
id: ${switchbot_tilt_3_name}
cover:
- platform: template
id: ${switchbot_tilt_1_name}_cover
device_class: curtain
name: ${switchbot_tilt_1_display_name}
optimistic: false
has_position: true
position_action:
- ble_client.ble_write:
id: ${switchbot_tilt_1_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: !lambda |-
uint8_t position = 100 * pos;
return {0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, position};
close_action:
- ble_client.ble_write:
id: ${switchbot_tilt_1_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x00]
open_action:
- ble_client.ble_write:
id: ${switchbot_tilt_1_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x32]
stop_action:
- ble_client.ble_write:
id: ${switchbot_tilt_1_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: !lambda |-
uint8_t position = 100 * id(${switchbot_tilt_1_name}_cover).position;
return {0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, position};
- platform: template
id: ${switchbot_tilt_2_name}_cover
device_class: curtain
name: ${switchbot_tilt_2_display_name}
optimistic: false
has_position: true
position_action:
- ble_client.ble_write:
id: ${switchbot_tilt_2_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: !lambda |-
uint8_t position = 100 * pos;
return {0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, position};
close_action:
- ble_client.ble_write:
id: ${switchbot_tilt_2_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x00]
open_action:
- ble_client.ble_write:
id: ${switchbot_tilt_2_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x32]
stop_action:
- ble_client.ble_write:
id: ${switchbot_tilt_2_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: !lambda |-
uint8_t position = 100 * id(${switchbot_tilt_2_name}_cover).position;
return {0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, position};
- platform: template
id: ${switchbot_tilt_3_name}_cover
device_class: curtain
name: ${switchbot_tilt_3_display_name}
optimistic: false
has_position: true
position_action:
- ble_client.ble_write:
id: ${switchbot_tilt_3_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: !lambda |-
uint8_t position = 100 * pos;
return {0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, position};
close_action:
- ble_client.ble_write:
id: ${switchbot_tilt_3_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x00]
open_action:
- ble_client.ble_write:
id: ${switchbot_tilt_3_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x32]
stop_action:
- ble_client.ble_write:
id: ${switchbot_tilt_3_name}
service_uuid: cba20d00-224d-11e6-9fb8-0002a5d5c51b
characteristic_uuid: cba20002-224d-11e6-9fb8-0002a5d5c51b
value: !lambda |-
uint8_t position = 100 * id(${switchbot_tilt_3_name}_cover).position;
return {0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, position};
Your millage may vary
P.S> No wiring is required, the ESP just needs to be close enough to have good Bluetooth. I have literally left mine on the shipping foam…