iMicknl / LoctekMotion_IoT

Learn how to connect your Flexispot (LoctekMotion) desk to the internet. This repository contains a collection of scripts to get your started, combined with research and instructions.
MIT License
625 stars 61 forks source link

FLEXISPOT EC5 - HS11A-1C0 + CB38M2H(PB)-1 compatibility ? #81

Open M3te0r opened 8 months ago

M3te0r commented 8 months ago

Hi, Do you know if the HS11A-1C0 + CB38M2H(PB)-1 combo is compatible? It has one RJ45 cable going from the control block to the control panel IMG20240317015952 IMG20240317015917 IMG20240317015820 Thanks!

M3te0r commented 8 months ago

IMG20240317025921 IMG20240317025913

M3te0r commented 8 months ago

Update : Tested out the HS13B-1 pins conf since they seemed to correspond with the control panel wiring, took a LOLIN S3 Mini and a RJ45 connector, and it worked !

I had to adapt some parts of the config to match pins, min/max height and the 3rd preset

Here is my yaml

esphome:
  name: flexispot
  friendly_name: Flexispot 
  includes:
    - desk_height_sensor.h

esp32:
  board: lolin_s3_mini
  variant: ESP32S3
  framework:
    version: latest
    platform_version: 6.5.0
    type: arduino

# Enable logging
logger:
  #level: DEBUG
  baud_rate: 0

# Enable Home Assistant API
api:
  encryption:
    key: "[REDACTED]"

ota:
  password: "[REDACTED]"

uart:
  id: desk_uart
  baud_rate: 9600
  tx_pin: 43
  rx_pin: 44

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"

switch:
  - platform: gpio
    name: "Virtual Screen"
    pin:
      number: 33
      mode: OUTPUT
    restore_mode: ALWAYS_ON
    internal: true

  - platform: uart
    name: "Preset 1"
    id: switch_preset1
    icon: mdi:numeric-1-box
    data: [0x9b, 0x06, 0x02, 0x04, 0x00, 0xac, 0xa3, 0x9d]
    uart_id: desk_uart

  - platform: uart
    name: "Preset 2"
    id: switch_preset2
    icon: mdi:numeric-2-box
    data: [0x9b, 0x06, 0x02, 0x08, 0x00, 0xac, 0xa6, 0x9d]
    uart_id: desk_uart

  - platform: uart
    name: "Preset 3"
    id: switch_preset3
    icon: mdi:numeric-3-box
    data: [0x9b, 0x06, 0x02, 0x10, 0x00, 0xac, 0xac, 0x9d]
    uart_id: desk_uart

  - platform: uart
    name: "Stand" # Not available for all control panels
    id: switch_stand
    icon: mdi:human-handsup
    data: [0x9b, 0x06, 0x02, 0x10, 0x00, 0xac, 0xac, 0x9d]
    uart_id: desk_uart

  - 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: 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
    internal: true

  - platform: uart
    name: "M"
    id: switch_m
    icon: mdi:alpha-m-circle
    data: [0x9b, 0x06, 0x02, 0x20, 0x00, 0xac, 0xb8, 0x9d]
    uart_id: desk_uart

  - 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

cover:
  - platform: template
    # icon: mdi:table-chair
    # icon: mdi-human-male-height-variant
    name: "Desk"
    assumed_state: true

    # Move desk up
    open_action:
      - while:
          condition:
            sensor.in_range:
              id: desk_height
              below: "126.9"
          then:
          - logger.log: "Executing up command"
          - switch.turn_on: switch_up
          - delay: 10ms

    # Move desk down
    close_action:
      - while:
          condition:
            sensor.in_range:
              id: desk_height
              above: "62.21"
          then:
          - logger.log: "Executing down command"
          - switch.turn_on: switch_down
          - delay: 10ms
    optimistic: true

Question is : I still want to use the keypad and there is only one RJ45 connector on the control block, the ESP32 can be powered on from the +5V, so could I just wire the ESP32 on the keypad PCB, right ?

What are the options if I want to keep the keypad ?

M3te0r commented 8 months ago

Update 2 Decided to buy 2 RJ45 female terminal connectors to handle the whole thing, looked at the esp32 keypad passthrough, a lot a yaml config files to handle position, cover stop, wake up command for move stop etc

ESP32 Loctek Controller (E5) Loctek Motion Cable Color (HS01B-1)
GND GND Blue
GPIO44 RX Green
GPIO43 TX Black
GPIO36 PIN 20 Red
ESP32 Loctek keypad (HS01B-1) Loctek Motion Cable Color (HS01B-1)
GND Blue (Bridged to GND from controller)
RX Green (Bridged to RX from controller)
GPIO35 TX Black
GPIO18 PIN 20 Red

It works great, i get to automate my desk and keep the keypad

I was getting some errors E07, E08 or Rst on the keypad, had to hold the down button till the desk reach the lowest height, but for now seems ok

Sometimes the esp switches or keypad buttons seem to not respond, looks like the esp32 restart itself, don't know why, even with reset_reason didn't get to understand why

IMG20240323183621

esphome:
  name: flexispot-2
  friendly_name: Flexispot 2
  includes:
    - desk_height_sensor.h
    - desk_keypad.h
  on_boot:
    priority: -100
    then:
      - wait_until:
          api.connected
      # force screen update to get height at api connect
      - script.execute: script_start_command
      - script.wait: script_start_command

esp32:
  board: lolin_s3_mini
  variant: ESP32S3
  framework:
    version: latest
    platform_version: 6.5.0
    type: arduino

debug:
  update_interval: 30s

text_sensor:
  - platform: debug
    device:
      name: "Device Info"
    reset_reason:
      name: "Reset Reason"

# Enable logging
logger:
  level: DEBUG
  baud_rate: 0

# Enable Home Assistant API
api:
  encryption:
    key: "[REDACTED]"

button:
  - platform: restart
    name: "Restart ESP32"

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

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: 630ms
        else:
          - script.execute: screen_timer

uart:
  - id: desk_uart
    baud_rate: 9600
    tx_pin: 43
    rx_pin: 44
  - id: keypad_uart
    baud_rate: 9600
    tx_pin: 1
    rx_pin: 35

sensor:

  - platform: internal_temperature
    name: "Internal Temperature"

  - 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

  - 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).make_call().set_command_open().perform();
              } else if(id(desk_command).state == 2)
              {
                id(desk).make_call().set_command_close().perform();
              } 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).make_call().set_command_stop().perform();
              }
            }

switch:
  - 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: template
    name: "Keypad locked"
    icon: mdi:key
    id: "keypad_switch"
    internal: false
    restore_mode: RESTORE_DEFAULT_OFF
    assumed_state: false
    optimistic: true

  - platform: gpio
    name: "Virtual Screen"
    id: "virtual_screen"
    pin:
      number: 36
      mode: OUTPUT
    restore_mode: ALWAYS_OFF
    internal: false

  - 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: uart
    name: "Stand" # Not available for all control panels
    id: switch_stand
    icon: mdi:human-handsup
    data: [0x9b, 0x06, 0x02, 0x10, 0x00, 0xac, 0xac, 0x9d]
    uart_id: desk_uart

  - 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: 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
    internal: true

  - platform: uart
    name: "M"
    id: switch_m
    icon: mdi:alpha-m-circle
    data: [0x9b, 0x06, 0x02, 0x20, 0x00, 0xac, 0xb8, 0x9d]
    uart_id: desk_uart

  - 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

    #MANUAL RESET
    #HOLD THE DOWN BUTTON UNTIL THE TABLE REACHES ITS LOWEST HEIGHT AND REBOUNDS
    #MIGHT NEED TO BE RUN MULTIPLE TIMES DEPENDING ON THE HEIGHT
  - platform: uart
    name: "Factory Reset" # Hold Down 5secs
    id: switch_factory_reset
    icon: mdi:factory
    data: [0x9b, 0x06, 0x02, 0x02, 0x00, 0x0c, 0xa0, 0x9d]
    uart_id: desk_uart
    send_every: 1ms
    on_turn_on:
      - delay: 5000ms
      - switch.turn_off: switch_factory_reset

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

    # target height mode
    position_action:
      - script.execute: script_start_command
      - script.wait: script_start_command
      # find if we are going up or down
      - if:
          condition:
            lambda: return float(id(desk).position) < pos;
          then:
          - while:
              condition:
                # if going up, offset current position to 2 percent higher than reality and loop
                lambda: return (float(id(desk).position) + 0.02) < pos;
              then:
                - cover.template.publish:
                    id: desk
                    current_operation: OPENING
                #- logger.log: "Executing up command"
                - switch.turn_on: switch_up
                - delay: 400ms
          else:
            - while:
                condition:
                  # if going down, offset current position to 2 percent lower than reality and loop
                  lambda: return (float(id(desk).position) - 0.02) > pos;
                then:
                  - cover.template.publish:
                      id: desk
                      current_operation: CLOSING
                  - switch.turn_on: switch_down
                  - delay: 400ms
      # clear the command queue with wake up command - https://github.com/iMicknl/LoctekMotion_IoT/issues/42#issuecomment-1416823108
      - switch.turn_on: switch_wake_up
      # publish idle status
      - cover.template.publish:
          id: desk
          current_operation: IDLE

    # Move desk up
    open_action:
      - script.execute: script_start_command
      - script.wait: script_start_command
      - while:
          condition:
            sensor.in_range:
              id: desk_height
              below: 126.9
          then:
          - logger.log: "Executing up command"
          - cover.template.publish:
              id: desk
              current_operation: OPENING
          - switch.turn_on: switch_up
          - delay: 400ms
      # clear the command queue with wake up command - https://github.com/iMicknl/LoctekMotion_IoT/issues/42#issuecomment-1416823108
      - switch.turn_on: switch_wake_up
      - cover.template.publish:
          id: desk
          state: OPEN
          current_operation: IDLE

    # Move desk down
    close_action:
      - script.execute: script_start_command
      - script.wait: script_start_command
      - while:
          condition:
            sensor.in_range:
              id: desk_height
              above: 62.1
          then:
          - logger.log: "Executing down command"
          - cover.template.publish:
              id: desk
              current_operation: CLOSING
          - switch.turn_on: switch_down
          - delay: 400ms   
      # clear the command queue with wake up command - https://github.com/iMicknl/LoctekMotion_IoT/issues/42#issuecomment-1416823108
      - switch.turn_on: switch_wake_up
      - cover.template.publish:
          id: desk
          state: CLOSED
          current_operation: IDLE

    # Stop Button
    stop_action:
      - logger.log: "Executing stop command"
      - switch.turn_on: switch_wake_up
      - cover.template.publish:
          id: desk
          current_operation: IDLE
    optimistic: false
ElCuboNegro commented 2 months ago

I'm glad to see I'm not the only one triying to work with this specific interface.

I'm triying to make a "men in the middle" with an arduino mega, just to know how everything is moving (also, this is my first electronics experiment)

Paulomart commented 1 month ago

Hey @M3te0r,

I have the same controller, but my desks stops when sending one down or up command (0x9b, 0x06, 0x02, 0x02, 0x00, 0x0c, 0xa0, 0x9d). When moving the desk it stops between movements, making the move unsteady. Do you also have the same issue?

Im using an ESP8266 ESP-12F NodeMcu board.

M3te0r commented 1 month ago

Hi @Paulomart The up/down commands have the same behaviour as the physical up/down button, a single slighty hold push will wake up the terminal and send a single up/down command, a longer push hold will continuously send the commands until you release the button

To simulate that hold behaviour you have to loop through a cycle of wake up, up/down, that's the intent behind the cover template, take a look at the open_action you might have to fine tune the delay, don't go too short or it will spam the terminal and you'll need to reset the controller

Paulomart commented 1 month ago

@M3te0r thanks I got it to work that way.

An issue I was facing: the sensor for the height was firing to often. I debounced the sensor and that seemed to help with smoother movement.

Here is my final setup:

api:
  encryption:
    key: "xxxxxxx"

ota:
  - platform: esphome
    password: "xxxxxxxx"

substitutions:
  min_height: "64" # cm
  max_height: "124" # cm
  screen_pin: D2
  tx_pin: D5
  rx_pin: D6
  friendly_name: "Office 1 Desk 1"
  name: "office-1-desk-1"

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

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

logger:

esp8266:
  board: nodemcuv2

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

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

captive_portal:

uart:
  id: desk_uart
  baud_rate: 9600
  tx_pin: ${tx_pin}
  rx_pin: ${rx_pin}

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

  - platform: uptime
    name: Uptime

  - platform: loctekmotion_desk_height
    id: "desk_height"
    name: Desk Height
    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:
        - component.update: set_desk_height

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

switch:
  - platform: gpio
    name: "Virtual Screen"
    pin:
      number: ${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

cover:
  - 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: 500ms
      - switch.turn_on: switch_up

    # Move desk down
    close_action:
      - if: 
          condition: 
            - switch.is_on: switch_up
          then: 
            - switch.turn_off: switch_up
            - delay: 500ms
      - 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