Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Flexispot ED5 #93

Open
amroncz opened this issue Aug 5, 2024 · 7 comments
Open

Add support for Flexispot ED5 #93

amroncz opened this issue Aug 5, 2024 · 7 comments

Comments

@amroncz
Copy link

amroncz commented Aug 5, 2024

Hello,

I am currently trying to make my Flexispot ED5 smart with the help of an ESP8266 and this repo.

The ED5 comes with the following control box:

IMG_20240531_135401.jpg

And the following keypad:
IMG_20240805_114650.jpg

I tried to adapt the wiring from this https://github.com/iMicknl/LoctekMotion_IoT/tree/main/packages/esphome#hs01b-1-with-esp32-as-passthrough-and-keypad-support
as follows:
image-1.png

I adapted the yaml file from https://github.com/iMicknl/LoctekMotion_IoT/blob/main/packages/esphome/flexispot_e5b_esp32.yaml and https://github.com/iMicknl/LoctekMotion_IoT/blob/main/packages/esphome/flexispot_e5b.yaml and it looks like this:

substitutions: device_name: Flexispot ED5 name: flexispot_ed5 min_height: "64.6" # Min height + 0.1 max_height: "129.9" # Max height - 0.1 esphome: name: ${name} comment: ${device_name} platform: ESP8266 # TODO Change to your platform board: nodemcuv2 # TODO Change to your board includes: - desk_height_sensor.h - desk_keypad.h wifi: ssid: !secret wifi_ssid password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails #ap: #ssid: "${device_name} Fallback Hotspot" #password: !secret ap_fallback_password captive_portal: # Enable logging logger: level: DEBUG baud_rate: 0 # Enable Home Assistant API api: password: !secret ha_api_password ota: platform: esphome #password: !secret ha_api_password script: - id: screen_timer mode: restart then: # on_start - logger.log: "Executing screen timer command" - switch.turn_on: virtual_screen - delay: 12s # timer length (keypad screen timeout is 10s + 2s buffer) - if: condition: binary_sensor.is_off: screen then: - switch.turn_off: virtual_screen # on_end - id: script_start_command mode: restart then: - logger.log: "Executing Empty command" - if: condition: switch.is_off: virtual_screen then: - script.execute: screen_timer - delay: 480ms else: - script.execute: screen_timer uart: - id: desk_uart baud_rate: 9600 tx_pin: TX rx_pin: RX - id: keypad_uart baud_rate: 9600 tx_pin: D6 # Why do I have to configure the TX pin here when it says here (https://github.com/iMicknl/LoctekMotion_IoT/tree/main/packages/esphome#hs01b-1-with-esp32-as-passthrough-and-keypad-support) "esp32: no pin, keypad: RX, bridged to RX from controller"? rx_pin: D5 # Used as second RX pin. Is this correct? sensor: - platform: wifi_signal name: "WiFi Signal" update_interval: 60s - platform: uptime name: Uptime - platform: custom lambda: |- auto desk_height_sensor = new DeskHeightSensor(id(desk_uart)); App.register_component(desk_height_sensor); return {desk_height_sensor}; sensors: id: "desk_height" name: Desk Height unit_of_measurement: cm accuracy_decimals: 1 icon: "mdi:counter" on_value: then: - script.execute: screen_timer # - cover.template.publish: # id: desk # position: !lambda |- # // The sensor outputs values from min_height (cm) to max_height (cm) # // We need to translate this to 0 - 1 scale. # float position = (float(x) - float(${min_height})) / (float(${max_height}) - float(${min_height})); # return position; - platform: custom lambda: |- auto desk_keypad_sensor = new DeskKeypad(id(keypad_uart)); App.register_component(desk_keypad_sensor); return {desk_keypad_sensor}; sensors: id: "desk_command" name: Desk command icon: "mdi:counter" on_value: then: lambda: |- if(!id(keypad_switch).state) { if(id(desk_command).state == 1) { id(desk).open(); } else if(id(desk_command).state == 2) { id(desk).close(); } else if(id(desk_command).state == 3) { id(switch_preset1).turn_on(); } else if(id(desk_command).state == 4) { id(switch_preset2).turn_on(); } else if(id(desk_command).state == 5) { id(switch_preset3).turn_on(); } else if(id(desk_command).state == 6) { id(switch_m).turn_on(); } else if(id(desk_command).state == 7) { id(switch_alarm).turn_on(); } else if(id(desk_command).state == 8) { id(desk).stop(); } } binary_sensor: - platform: gpio name: "Screen" id: "screen" pin: D1 internal: false on_press: then: - switch.turn_on: virtual_screen on_release: then: - switch.turn_off: virtual_screen switch: - platform: template name: "Keypad locked" icon: mdi:key id: "keypad_switch" internal: false #restore_state: true restore_mode: RESTORE_DEFAULT_ON assumed_state: false optimistic: true - platform: template name: "Preset 1" id: switch_preset1 icon: mdi:numeric-1-box turn_on_action: - logger.log: "Executing Preset 1 command" - script.execute: script_start_command - script.wait: script_start_command - uart.write: id: desk_uart data: [0x9b, 0x06, 0x02, 0x04, 0x00, 0xac, 0xa3, 0x9d] internal: false - platform: template name: "Preset 2" id: switch_preset2 icon: mdi:numeric-2-box turn_on_action: - logger.log: "Executing Preset 2 command" - script.execute: script_start_command - script.wait: script_start_command - uart.write: id: desk_uart data: [0x9b, 0x06, 0x02, 0x08, 0x00, 0xac, 0xa6, 0x9d] internal: false - platform: template name: "Preset 3" id: switch_preset3 icon: mdi:numeric-3-box turn_on_action: - logger.log: "Executing Preset 3 command" - script.execute: script_start_command - script.wait: script_start_command - uart.write: id: desk_uart data: [0x9b, 0x06, 0x02, 0x10, 0x00, 0xac, 0xac, 0x9d] internal: false - platform: template name: "M" id: switch_m icon: mdi:alpha-m-circle turn_on_action: - logger.log: "Executing Preset 3 command" - script.execute: script_start_command - script.wait: script_start_command - uart.write: id: desk_uart data: [0x9b, 0x06, 0x02, 0x20, 0x00, 0xac, 0xb8, 0x9d] internal: false - platform: template name: "Alarm" id: switch_alarm icon: mdi:alpha-m-circle turn_on_action: - logger.log: "Executing Alarm command" # - script.execute: script_start_command # - script.wait: script_start_command # - uart.write: # id: desk_uart # data: [0x9b, 0x06, 0x02, 0x40, 0x00, 0xac, 0x90, 0x9d] internal: false - platform: gpio name: "Virtual Screen" id: "virtual_screen" pin: number: D2 mode: OUTPUT restore_mode: ALWAYS_OFF internal: false - platform: uart name: "Up" id: switch_up icon: mdi:arrow-up-bold data: [0x9b, 0x06, 0x02, 0x01, 0x00, 0xfc, 0xa0, 0x9d] uart_id: desk_uart internal: false - platform: uart name: "Down" id: switch_down icon: mdi:arrow-down-bold data: [0x9b, 0x06, 0x02, 0x02, 0x00, 0x0c, 0xa0, 0x9d] uart_id: desk_uart internal: false - platform: uart name: "Empty command" id: switch_empty data: [0x9b, 0x06, 0x02, 0x00, 0x00, 0x6c, 0xa1, 0x9d] uart_id: desk_uart internal: false cover: - platform: template name: "Desk" id: "desk" assumed_state: true # has_position: true # position_action: # - logger.log: "Requesting action change" # Move desk up open_action: - script.execute: script_start_command - script.wait: script_start_command - while: condition: sensor.in_range: id: desk_height below: ${max_height} then: - logger.log: "Executing up command" # - cover.template.publish: # id: desk # current_operation: OPENING - switch.turn_on: switch_up - delay: 108ms # Move desk down close_action: - script.execute: script_start_command - script.wait: script_start_command - while: condition: sensor.in_range: id: desk_height above: ${min_height} then: - logger.log: "Executing down command" # - cover.template.publish: # id: desk # current_operation: CLOSING - switch.turn_on: switch_down - delay: 108ms optimistic: true

I have two questions about the yaml file:

1: Why do I have to configure the TX pin for the keypad if it says here (https://github.com/iMicknl/LoctekMotion_IoT/tree/main/packages/esphome#hs01b-1-with-esp32-as-passthrough-and-keypad-support): "esp32: no pin, keypad: RX, bridged to RX from controller"? So I used the unused D6 as a dummy... This is probably completely stupid.

2: My ESP8266 has only one TX and one RX (I think it's normal?), so I use D5 as a second RX pin for keypad. Is this correct?

I was able to successfully flash my ESP8266 via ESPHome with my configuration, but nothing works and I probably fried my ESP8266. I suspect that the wiring is not correct. Here is a more detailed overview of the wiring:

desk_wiring.drawio.png

@neildsb
Copy link

neildsb commented Nov 15, 2024

maybe this can help? Get out the oscilloscope

https://youtu.be/1IRBaRmFxDU?si=rlBDDUVq_YBBJawE

@jamesmyatt
Copy link
Contributor

You should find the model of the keypad somewhere on the PCB. Otherwise you can trace the wires using a continuity tester and follow the "passthrough" instructions in the v1 archive: https://github.com/iMicknl/LoctekMotion_IoT/tree/main/archive/esphome.

Your ESP8266 also needs power. You can use the 5V from the desk if you connect it to the 5V input on your ESP8266 dev board.

You also need to bridge all of the other connections between the desk and the keypad.

@amroncz
Copy link
Author

amroncz commented Nov 16, 2024

maybe this can help? Get out the oscilloscope

https://youtu.be/1IRBaRmFxDU?si=rlBDDUVq_YBBJawE

I have also subscribed to the channel, but haven't seen the video yet. I will definitely watch it. Thank you.

@amroncz
Copy link
Author

amroncz commented Nov 16, 2024

You should find the model of the keypad somewhere on the PCB.

It's a little hard to see because there's this circular spring thing above the writing. It's something like "HS13U-...".

Otherwise you can trace the wires using a continuity tester

How does that work?

and follow the "passthrough" instructions in the v1 archive: https://github.com/iMicknl/LoctekMotion_IoT/tree/main/archive/esphome.

On the page I can only find the wiring for Loctek Motion (HS13A-1) Loctek Motion (HS01B-1). This will probably be completely different for me?

@MattFryer
Copy link

@amroncz Did you get this working in the end? I have a different control panel but the same control box as you. I've worked out the pinout and managed to capture all the codes for the buttons. Interestingly I get a different pin out for the RJ45 to the one in your wiring diagram:

8 -> 5v
7 -> GND
6 -> RX
5 -> TX
4 -> Pin20

I was hoping to just define a second UART and echo the traffic across them but the current code fails to compile with a second UART. Going to spend this weekend looking at either modifying the existing custom component to allow 2 UARTs or implementing the v1 code. I thought I'd find out if you managed to get it working?

@amroncz
Copy link
Author

amroncz commented Jan 22, 2025

@amroncz Did you get this working in the end? I have a different control panel but the same control box as you. I've worked out the pinout and managed to capture all the codes for the buttons. Interestingly I get a different pin out for the RJ45 to the one in your wiring diagram:

8 -> 5v
7 -> GND
6 -> RX
5 -> TX
4 -> Pin20

I was hoping to just define a second UART and echo the traffic across them but the current code fails to compile with a second UART. Going to spend this weekend looking at either modifying the existing custom component to allow 2 UARTs or implementing the v1 code. I thought I'd find out if you managed to get it working?

Unfortunately, I still haven't managed to do it. After I fried 3 of my ESPs and got no help here or in the Discord, I put the project on hold for the time being.
I'll probably have to move on to simpler projects of this kind first, as I'm a complete noob.
If you find out anything, please let me know anyway.

@amroncz
Copy link
Author

amroncz commented Feb 6, 2025

With the help of Yama from the Discord, I've made a lot of progress.
My wiring currently looks like this:

Image

And this is my code:

substitutions:
  min_height: "60" # cm
  max_height: "120" # cm
  c_screen_pin: D2
  c_tx_pin: D5
  c_rx_pin: D6
  k_screen_pin: D7
  k_tx_pin: D0
  k_rx_pin: D1 # unused
  friendly_name: "Flexispot ED5"
  name: "flexispot-ed5"

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  includes:
    - desk_height_sensor.h
    - desk_keypad.h
  on_boot:
    priority: -10
    then:
      - switch.turn_on: switch_wake_up

external_components:
  source: github://iMicknl/LoctekMotion_IoT
  components: [loctekmotion_desk_height]

logger:
  level: DEBUG
  baud_rate: 0

esp8266:
  board: nodemcuv2

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  manual_ip:
    static_ip: 192.168.2.149
    gateway: 192.168.2.1
    subnet: 255.255.255.0

  ap:
    ssid: "recover me ${friendly_name}"

captive_portal:

api:
  password: !secret ha_api_password

ota:
  - platform: esphome

uart:
  - id: desk_uart
    baud_rate: 9600
    tx_pin: ${c_tx_pin}
    rx_pin: ${c_rx_pin}

  - id: keypad_uart
    baud_rate: 9600
    tx_pin: ${k_tx_pin}
    rx_pin: ${k_rx_pin}

script:
  - id: screen_timer
    mode: restart
    then:
      # on_start
      - logger.log: "Executing screen timer command"
      - switch.turn_on: virtual_screen
      - delay: 12s # timer length (keypad screen timeout is 10s + 2s buffer)
      - if:
          condition:
            binary_sensor.is_off: screen
          then:
            - switch.turn_off: virtual_screen
      # on_end

  - id: script_start_command
    mode: restart
    then:
      - logger.log: "Executing Empty command"
      - if:
          condition:
            switch.is_off: virtual_screen
          then:
            - script.execute: screen_timer
            - delay: 480ms
          else:
            - script.execute: screen_timer

binary_sensor:
  - platform: gpio
    name: "Screen"
    id: "screen"
    pin: ${k_screen_pin}
    internal: false
    on_press:
      then:
        - switch.turn_on: virtual_screen
    on_release:
      then:
        - switch.turn_off: virtual_screen

sensor:
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 60s

  - platform: uptime
    name: Uptime

  - platform: loctekmotion_desk_height
    id: "desk_height"
    name: Desk Height
    uart_id: desk_uart
    filters:
      - clamp:
          min_value: ${min_height}
          max_value: ${max_height}
          ignore_out_of_range: true
      - debounce: 10ms
    on_value_range:
      - below: ${min_height}
        then:
          - switch.turn_off: switch_down
      - above: ${max_height}
        then:
          - switch.turn_off: switch_up
    on_value:
      then:
        - script.execute: screen_timer
        - component.update: set_desk_height

  - platform: custom
    lambda: |-
      auto desk_keypad_sensor = new DeskKeypad(id(keypad_uart));
      App.register_component(desk_keypad_sensor);
      return {desk_keypad_sensor};
    sensors:
      id: "desk_command"
      name: Desk command
      icon: "mdi:counter"
      on_value:
        then:
          lambda: |-
            if(id(desk_command).state == 1)
            {
              id(button_up).press();
            } else if(id(desk_command).state == 2)
            {
              id(button_down).press();
            } else if(id(desk_command).state == 3)
            {
              id(button_stand).press();
            } else if(id(desk_command).state == 4)
            {
              id(button_sit).press();
            } else if(id(desk_command).state == 5)
            {
              id(button_3).press();
            }  else if(id(desk_command).state == 6)
            {
              id(button_4).press();
            } else if(id(desk_command).state == 7)
            {
              id(button_m).press();
            } else if(id(desk_command).state == 8)
            {
              id(desk_cover).make_call().set_command_stop().perform();
            }

button:
  - platform: uart
    name: "Step Up"
    id: button_up
    icon: mdi:arrow-up-bold
    data: [0x9b, 0x06, 0x02, 0x01, 0x00, 0xfc, 0xa0, 0x9d]
    uart_id: desk_uart

  - platform: uart
    name: "Step Down"
    id: button_down
    icon: mdi:arrow-down-bold
    data: [0x9b, 0x06, 0x02, 0x02, 0x00, 0x0c, 0xa0, 0x9d]
    uart_id: desk_uart

  - platform: template
    name: "Stand"
    icon: mdi:numeric-1-box
    id: button_stand
    on_press:
      - uart.write:
          id: desk_uart
          data: [0x9b, 0x06, 0x02, 0x04, 0x00, 0xac, 0xa3, 0x9d]

  - platform: template
    name: "Sit"
    icon: mdi:numeric-2-box
    id: button_sit
    on_press:
      - uart.write:
          id: desk_uart
          data: [0x9b, 0x06, 0x02, 0x08, 0x00, 0xac, 0xa6, 0x9d]

  - platform: template
    name: "Preset 3"
    icon: mdi:chair-rolling
    id: button_3
    on_press:
      - uart.write:
          id: desk_uart
          data: [0x9b, 0x06, 0x02, 0x00, 0x01, 0xac, 0x60, 0x9d]

  - platform: template
    name: "Preset 4" # Preset 4 on some control panels
    icon: mdi:human-handsup
    id: button_4
    on_press:
      - uart.write:
          id: desk_uart
          data: [0x9b, 0x06, 0x02, 0x10, 0x00, 0xac, 0xac, 0x9d]

  - platform: template
    name: "Memory"
    id: button_m
    icon: mdi:alpha-m-box
    entity_category: "config"
    on_press:
      - uart.write:
          id: desk_uart
          data: [0x9b, 0x06, 0x02, 0x20, 0x00, 0xac, 0xb8, 0x9d]

  - platform: template
    name: "Wake Screen"
    id: button_wake_screen
    icon: mdi:gesture-tap-button
    entity_category: "config"
    on_press:
      - uart.write:
          id: desk_uart
          data: [0x9b, 0x06, 0x02, 0x00, 0x00, 0x6c, 0xa1, 0x9d]

switch:
  - platform: gpio
    name: "Virtual Screen"
    id: "virtual_screen"
    pin:
      number: ${c_screen_pin}
      mode: OUTPUT
    restore_mode: ALWAYS_ON
    internal: true

  - platform: uart
    name: "Up"
    id: switch_up
    icon: mdi:arrow-up-bold
    data: [0x9b, 0x06, 0x02, 0x01, 0x00, 0xfc, 0xa0, 0x9d]
    uart_id: desk_uart
    send_every: 1ms
    on_turn_on:
      then:
        - switch.turn_off: switch_down
    internal: true

  - platform: uart
    name: "Down"
    id: switch_down
    icon: mdi:arrow-down-bold
    data: [0x9b, 0x06, 0x02, 0x02, 0x00, 0x0c, 0xa0, 0x9d]
    uart_id: desk_uart
    send_every: 1ms
    on_turn_on:
      then:
        - switch.turn_off: switch_up
    internal: true

  - platform: uart
    name: "(wake up)" # Not available on all control panels
    id: switch_wake_up
    icon: mdi:gesture-tap-button
    data: [0x9b, 0x06, 0x02, 0x00, 0x00, 0x6c, 0xa1, 0x9d]
    uart_id: desk_uart
    internal: true

  - platform: uart
    name: "Empty command"
    id: switch_empty
    data: [0x9b, 0x06, 0x02, 0x00, 0x00, 0x6c, 0xa1, 0x9d]
    uart_id: desk_uart
    internal: false

cover:
  #- platform: template
  #  icon: mdi:table-chair
  #  # icon: mdi-human-male-height-variant
  #  name: "Desk"
  #  id: "desk"
  #  assumed_state: true
  #  has_position: true
  #  # set current position percent
  #  lambda: return (float(id(desk_height).state) - float(62.1)) / (float(126.9) - float(62.1));

  - platform: template
    id: desk_cover
    icon: mdi:table-chair
    name: "Desk"
    assumed_state: true
    optimistic: true

    # Move desk up
    open_action:
      - if:
          condition:
            - switch.is_on: switch_down
          then:
            - switch.turn_off: switch_down
            - delay: 100ms
      - switch.turn_on: switch_up

    # Move desk down
    close_action:
      - if:
          condition:
            - switch.is_on: switch_up
          then:
            - switch.turn_off: switch_up
            - delay: 100ms
      - switch.turn_on: switch_down

    stop_action:
      then:
        - switch.turn_off: switch_down
        - switch.turn_off: switch_up

number:
  - platform: template
    name: "Desk Height"
    id: set_desk_height
    min_value: ${min_height}
    max_value: ${max_height}
    icon: "mdi:counter"
    unit_of_measurement: "cm"
    device_class: "distance"
    step: 0.1
    lambda: !lambda |-
      return id(desk_height).state;
    set_action:
      - if:
          condition:
            - lambda: !lambda |-
                return x > id(desk_height).state;
          then:
            # direction up
            - cover.open: desk_cover
            - wait_until:
                lambda: |-
                  return id(desk_height).state  >= x - 1;
            - cover.stop: desk_cover
            - delay: 1s
            # move a little down again to adjust for momentum
            - uart.write:
                id: desk_uart
                data: [0x9b, 0x06, 0x02, 0x02, 0x00, 0x0c, 0xa0, 0x9d] # down
          else:
            # direction down
            - cover.close: desk_cover
            - wait_until:
                lambda: |-
                  return id(desk_height).state <= x + 3;
            - cover.stop: desk_cover
            - delay: 1s
            # move a little up again to adjust for momentum
            - uart.write:
                id: desk_uart
                data: [0x9b, 0x06, 0x02, 0x01, 0x00, 0xfc, 0xa0, 0x9d] # up

I have the following problem:
If I connect ESP D5 and controller 6, everything but keypad inputs work.
If I connect controller 6 and keypad 6, everything but home assistant inputs work.
But if I connect all 3, nothing works. So how can I get the best of both worlds?
It looks as if the keypad cannot send any data. Nothing is logged when a button is pressed. So maybe keypad 6 is wired incorrectly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants