[分享] 樂歌 Loctek 升降桌 ET119 WIFI 控制器 (ESPHome)

樂歌 Loctek 升降桌 ET119 有提供 UART 通訊介面,透過 ESP 開發板與 ESPHome 可加入 Home Assistant 進行遠端與自動化控制

一、通訊架構

HCB223A-1 <–UART–> ESP開發板(ESPhome) <–WIFI–> Home Assistant



二、ESPHome

( DT-06 開發板 ESPHome 燒錄流程, 請參考另一篇分享)

standing-desk.yaml

substitutions:
  device_name: standing-desk
  friendly_name: 升降桌
  min_height: "73"
  max_height: "123"
  moving_threshold: "1000"

packages:
  board: !include board/doit_dt_06.yaml
  device: !include base.yaml

esphome:
  comment: DT-06 for Loctek ET119
  on_boot:
    priority: -10
    then:
      - button.press: button_m

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

logger:
  level: DEBUG
  baud_rate: 0

uart:
  - id: desk_uart
    rx_pin: GPIO3
    tx_pin: GPIO1
    baud_rate: 9600

globals:
  - id: move_at
    type: long
    initial_value: '0'

sensor:
  - platform: loctekmotion_desk_height
    id: "desk_height"
    name: Desk Height
    on_value_range:
    - below: ${min_height}
      then:
        - switch.turn_off: switch_down
    - above: ${max_height}
      then:
        - switch.turn_off: switch_up
    on_value:
      then:
        - cover.template.publish:
            id: desk_cover
            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;
        - component.update: set_desk_height
        - lambda: |-
            id(move_at) = millis();

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:
            - cover.open: desk_cover
            - wait_until:
                lambda: |-
                  return id(desk_height).state  >= x;
            - cover.stop: desk_cover
          else:
            - cover.close: desk_cover
            - wait_until:
                lambda: |-
                  return id(desk_height).state <= x;
            - cover.stop: desk_cover

binary_sensor:
  - platform: template
    id: moving
    name: "Moving"
    device_class: "moving"
    lambda: |-
      return millis() - id(move_at) <= ${moving_threshold};

    on_state:
      if:
        condition:
          binary_sensor.is_on: moving
        then:
          - light.turn_on: board_blue_led
        else:
          - light.turn_off: board_blue_led
          - switch.turn_off: switch_down
          - switch.turn_off: switch_up

switch:
  - 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: 108ms
    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: 108ms
    internal: true

  - platform: uart
    name: "Alarm off"
    id: switch_alarm
    icon: mdi:alarm
    data: [0x9b, 0x06, 0x02, 0x40, 0x00, 0xAC, 0x90, 0x9d]
    uart_id: desk_uart
    send_every: 108ms
    disabled_by_default: true
    entity_category: "config"
    on_turn_on:
      - delay: 3000ms
      - switch.turn_off: switch_alarm

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

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

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

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

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

  - platform: template
    name: "Alarm"
    id: button_alarm
    icon: mdi:alarm
    disabled_by_default: true
    on_press:
      - uart.write:
          id: desk_uart
          data: [0x9b, 0x06, 0x02, 0x40, 0x00, 0xAC, 0x90, 0x9d]

cover:
  - platform: template
    id: "desk_cover"
    icon: mdi:desk # or mdi:human-male-height-variant
    name: "Desk"
    device_class: blind # makes it easier to integrate with Google/Alexa
    has_position: true
    position_action:
      - if:
          condition:
            - lambda: !lambda |-
                return pos > id(desk_cover).position;
          then:
            - cover.open: desk_cover
            - wait_until:
                lambda: |-
                  return id(desk_cover).position  >= pos;
            - cover.stop: desk_cover
          else:
            - cover.close: desk_cover
            - wait_until:
                lambda: |-
                  return id(desk_cover).position <= pos;
            - cover.stop: desk_cover
    stop_action:
      - switch.turn_off: switch_up
      - switch.turn_off: switch_down
      - button.press: button_m
    open_action:
      - switch.turn_off: switch_down
      - switch.turn_on: switch_up
    close_action:
      - switch.turn_off: switch_up
      - switch.turn_on: switch_down
    optimistic: false

doit_dt_06.yaml

# Chip is ESP8285N08
# Features: WiFi, Embedded Flash
# Crystal is 26MHz
# Detected flash size: 1MB

esp8266:
  board: esp8285

light:
  - platform: status_led
    name: "Status LED"
    id: board_blue_led
    output: light_output

output:
  - platform: gpio
    id: light_output
    pin:
      number: GPIO4
      inverted: True

base.yaml

esphome:
  name: $device_name
  friendly_name: $friendly_name

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

  ap:
    ssid: "$friendly_name"
    password: "12345678"

captive_portal:

ota:
  - platform: esphome

api:

web_server:
  version: 3
  port: 80

secrets.yaml

# Your Wi-Fi SSID and password
wifi_ssid: "WIFI_SSID"
wifi_password: "WIFI_PASSWORD"

三、接線

HCB223A-1 可用 RJ45 接頭,或直接焊接 RJ45 接頭背後端點



HCB223A-1 <> DT-06
(RJ45 Pin8)+5v <> VCC
(RJ45 Pin7)GND <> GND
(RJ45 Pin6)RXD <> TXD
(RJ45 Pin5)TXD <> RXD

四、 Entities Card

image

type: entities
entities:
  - entity: sensor.standing_desk_desk_height
    type: custom:multiple-entity-row
    name: 升降桌
    styles:
      width: 45px
    state_color: true
    icon: mdi:desk
    entities:
      - icon: mdi:arrow-up-bold-box
        state_color: true
        styles:
          width: 15px
        tap_action:
          action: call-service
          service: cover.open_cover
          service_data:
            entity_id: cover.standing_desk_desk
      - icon: mdi:pause-box
        state_color: true
        styles:
          width: 15px
        tap_action:
          action: call-service
          service: cover.stop_cover
          service_data:
            entity_id: cover.standing_desk_desk
      - icon: mdi:arrow-down-bold-box
        state_color: true
        styles:
          width: 15px
        tap_action:
          action: call-service
          service: cover.close_cover
          service_data:
            entity_id: cover.standing_desk_desk
      - icon: mdi:numeric-1-box
        state_color: true
        styles:
          width: 15px
        tap_action:
          action: call-service
          service: button.press
          service_data:
            entity_id: button.standing_desk_preset_1
      - icon: mdi:numeric-2-box
        state_color: true
        styles:
          width: 15px
        tap_action:
          action: call-service
          service: button.press
          service_data:
            entity_id: button.standing_desk_preset_2
      - icon: mdi:numeric-3-box
        state_color: true
        styles:
          width: 15px
        tap_action:
          action: call-service
          service: button.press
          service_data:
            entity_id: button.standing_desk_preset_3
      - icon: mdi:numeric-4-box
        state_color: true
        styles:
          width: 15px
        tap_action:
          action: call-service
          service: button.press
          service_data:
            entity_id: button.standing_desk_preset_4

參考資訊