Support for Switchbot Blind Tilt

There’s a couple of issues with the integration with HA. The UI buttons don’t really match the actual open/close function of the service. In the current PR, the services opens to 50% and closes to either 0% or 100% depending on the current position, but the UI is expecting open to be 100% and closed 0% so you’ll end up with odd behaviour clicking the UI buttons. I did an alternative implementation that makes the UI buttons work as you’d expect (open goes to 50%, then 100% clicking again, closed goes 50% then 0%), but the service calls don’t line up (open could result in closed, closed could result in open).

There are two solutions that I’ve come up with:

  1. Only support a single closed direction for the blind tilt. Map 50% open from the blind tilt to 100% in HA. This simplifies the integration, but limits the functionality. Users would pick one direction by recalibrating the blinds so that closed down is the desired closed direction.
  2. Add a close up tilt service that if supported changes the UI to buttons to be more reflective of the actual blinds behaviour. close_tilt = 0%, open_tilt = 50%, close_up_tilt = 100%. Up button calls open or close_up depending on blind position and down button calls open or close. This is the more complicated integration and modifies the core cover entity.

I did a quick sample implementation of each approach. The latter is far from complete and needs tests and such, but should hopefully be enough to explain what I mean. I also haven’t actually tested these yet, they’re just proposed solutions.
Approach 1: Back-end

1 Like

Approach 2: Back-end Front-end

1 Like

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 :wink: 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…

5 Likes

It works! Thnx!

This is awesome. For some reason, my open icon for the device remains greyed out all the time. The close icon, stop icon, and position slider are available and work as it should. I have double-checked the configuration and I don’t see anything glaring.

Any suggestions?

EDIT: I did some more testing. Clicking the close button closes the blinds “closed down.” Clicking the stop icon does not stop movement, it makes it go to “closed up.” I used the browser inspector tool and removed all the “disabled” attributes from the open button. It opens the blinds to 50%. Reloading the page greys out the open button.
This what I have in my yaml file.

    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};

Here is my actual device info showing the greyed out button:
image

Scott

Well… I THOUGHT that the Google SDK was the workaround - nope. Whether I click on “Open Living Room Blinds” (sending the text command to Google), or if I put it into an automation - HA just gets ornry! The blinds open, yes, but:

Here’s an example from the log entries, every 10 seconds:
8:39:43 AM - Started triggered by automation Morning Routine triggered by state of Kitchen Motion Sensor state (Any)
8:39:43 Turned on triggered by automation Morning Routine triggered by state of Kitchen Motion Sensor state (Any)
8:40:01 AM - Turned off
8:40:01 AM - Started triggered by Google Assistant sent command ActivateScene (via local)
8:40:01 AM - Started triggered by Google Assistant sent command ActivateScene
8:40:01 AM - Turned on triggered by Google Assistant sent command ActivateScene
8:40:18 AM - Turned off
8:40:18 AM - Started triggered by Google Assistant sent command ActivateScene (via local)
8:40:18 AM - Started triggered by Google Assistant sent command ActivateScene
8:40:18 AM - Turned on triggered by Google Assistant sent command ActivateScene
8:40:35 AM - Turned off
8:40:35 AM - Started triggered by Google Assistant sent command ActivateScene (via local)
8:40:35 AM - Started triggered by Google Assistant sent command ActivateScene
8:40:35 AM - Turned on triggered by Google Assistant sent command ActivateScene

8:40:52, 8:41:09, 8:41:21, 8:41:32 and etc etc etc.

And then I have to go and actually restart the PC because HA is unresponsive.

Hi!
So the stop action is a bit of a hack, it just gets the current position then commands tilt to go there… IE: move to where it is currently at because I didn’t know how a stop command to tilt was structured. So I am not surprised it was a little funky, wouldn’t expect it to go to “closed up”… I don’t have a good suggestion for that one but I have been on the lookout for a breakdown of the Bluetooth commands/protocol for tilt and will update accordingly if I find one. Might also see if I can figure it out but I don’t have an easy way to capture the switchbot app interactions atm.

With regards to the buttons in HA, for me 0% HA considers closed and anything else it seems to consider (at least partially) open so my options do toggle… I am on HA core 2023.1.6, not sure if that makes a difference. I did just realize that if you “close up” that will report 100% and HA will see it as open, perhaps this only works with “closed down”. I would recommend trying to use “close down”, manually positioning to 0% and see if the icon enables. Another thought would be if your tilt needs to be re-calibrated because maybe its not reaching 0% when closed (maybe check by hitting close and see what the position percentage is).

Last thought, you could set up a helper button for open and close, use those on your dashboard, as a temporary work-around…

Anyone who wants their blinds to always close up instead of down can change the last part of the close command value to 0x64 instead of 0x00.

Personally, I close my binds to block sunlight so having the surface of the blind facing the sun instead of the edge is how I use them.

Is there any way to add error handling to this? It seems if it sends the command and the blind doesn’t execute it, nothing will ever happen, no second attempt. Not really useful for automation especially since we don’t have a way of reading the position or status in a way that is meaningful.

Here is the error in the device logs:

[W][ble_client.automation:015]: Cannot write to BLE characteristic - not connected

This YAML fixes the issues with the original one that was causing weird behavior with the buttons and position. It works for all my Blind Tilts on Firmware V2.

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 (strcmp(data.uuid.to_string().c_str(), "0x0969") == 0 && data.data.size() ==  11) {
                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 (strcmp(data.uuid.to_string().c_str(), "0x0969") == 0 && data.data.size() ==  11) {
                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 (strcmp(data.uuid.to_string().c_str(), "0x0969") == 0 && data.data.size() ==  11) {
                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};

By golly I think this script fixed the issue I was having. Thanks!

1 Like

Bluetooth Proxy is really easy to setup, and works really well with Switchbot devices. The only trouble is, the Switchbot Blind Tilt, which this feature request is about, is not yet supported in the Home Assistant Switchbot bluetooth integration.

There is a PR to have it added currently open, maybe this is worth waiting for.
Implement Switchbot Blind Tilt by jesserockz · Pull Request #86591 · home-assistant/core (github.com)

2 Likes

To be clear, it looks like the only change is from:

if (data.uuid.to_string() == "0x0969" && data.data.size() ==  10) {

to:

if (data.uuid.to_string() == "0x0969" && data.data.size() ==  11) {

Is that correct? I ask because it’s a lot easier to make a couple tweaks than rename everything I’ve already changed in the sketch.

Yes, that’s the only change

Support through the switchbot integration should be added in the next release of HA, thanks to all contributors!

7 Likes

Has anyone gotten this working on homekit? I was able to get the template working but homekit only shows the status as closed and I can not slide it. If I go into the homekit device settings, I can adjust the “angle”

You may have to set ‘optimistic’ in your definition for your template cover. I had to do that in order to get both the open and close buttons to work. That may be the issue for HomeKit as homeassistant may not permit the open function without it.

Does anyone know which version of Home Assistant this will be in?

1 Like

I am still not able to get it working properly. This caused another device to show up that was called “Blinds” and wouldn’t operate the physical blinds. But it would show up in homekit like normal and operate like normal. This is my config file. Let me know what I should change

cover:
  - platform: template
    covers:
      blind_tilt_4834:
        device_class: blind
        friendly_name: Blinds
        optimistic: true
        open_cover:
          service: cover.set_cover_tilt_position
          data:
            tilt_position: 50
          target:
            entity_id: cover.example_blinds
        close_cover:
          service: cover.set_cover_tilt_position
          data:
            tilt_position: 0
          target:
            entity_id: cover.example_blinds
        position_template: >
          {{ int(states.cover.example_blinds.attributes.current_tilt_position)*2 }}
        set_cover_position:
          service: cover.set_cover_tilt_position
          data:
            tilt_position: "{{position/2}}"
          target:
            entity_id: cover.example_blinds

This will be in the new 2023.3 release. Currently in beta. 2023.3: Dialogs! - Home Assistant