본문 바로가기
IoT

HomeAssistant 샤오미 이라이트 무드등 BLE 연동하기

by ㅋㅋ잠자 2020. 8. 2.
반응형

안녕하세요? 


최근에 무드등이 있으면 좋을 것 같아서 중고나라에서 하나 샀습니다. 가격이 매우 싸서 하나 물었는데요.


알고보니, 이게 BT 방식은 HA와 연동이 안되는 ... ㅠㅠ


XM 로 시작하는 제품은 BT 전용이라 안되고 MC 로 시작하는 제품은 와이파이라 연동이 됩니다.




그런데... HA는 만능 플랫폼이라 어떻게든 연동이 가능합니다.


일단은.. 처음에는 ble 로 연동하기 위해서 혹시 제가 yeelight daemon 을 짜야할지 아니면 짜져 있는 것을 사용할지 서칭을 시작합니다.





1. 개봉기


중고라서 이미 개봉이 된 점 아쉽게 생각합니다.











2. 라이브러리 서칭


일단은 2가지가 보입니다.


1. HomeBridge 애드온을 통해 연동


https://www.npmjs.com/package/homebridge-re-yeelight


흠.. 이것 저것 삽질 해보고 남기는 글이라 매우 간단하게 나마 작성하는 부분이 있습니다.


일단은 상기 홈프릿지 애드온을 사용하기 위해서 홈브릿지를 설치하고 모듈을 올린다음에 악세사리에 올라오지 않아서.. 이걸 아이폰으로 연결해서 해야하는지 잘 모르겠습니다.


그리고 HA 와 홈킷 컨트롤러로 연동하려니 PASS키가 맞지 않다고 연동이 되지 않아서 포기하게 되었습니다. 혹시 방법이 있다면 이 방식이 제일 좋을 것 같은데 잘 안되었네요.


2. 파이썬 모듈을 통해 연동


하기 모듈로 커맨드 라인으로 컨트롤이 가능합니다.


https://github.com/rytilahti/python-yeelightbt


이 모듈은 작동이 아주 잘 되지는 않았습니다. 커맨드를 날릴 때 마다 BLE 연결을 시도하기 때문에 딜레이가 있습니다.

파이썬을 배우려고 하려다 보니, 문법이 너무 ㅠㅠ 제가 잘 몰라서 그렇겠죠?


아무래도 이 모듈을 참고해서 yeelightbt-daemon 등으로 REST API 를 구현하여 시그널을 받도록 구현하거나 MQTT 로도 가능할 것 같은데 일단은 제가 능력 부족으로 접기로 했습니다.


그렇다고 C로 작성하기도 애매하고 그래서 ㅎㅎㅎㅎ 그냥 사실 때 와이파이 버전을 사시는게 제일 좋아 보입니다.


저는 이미 샀으니 어쩔 수가 없어서..





3. yeelightbt 모듈 사용하기


일단은 yeelightbt 모듈은 python3 만 지원합니다.


일반 데비안에서는 pip3 로 설치해야하고, HA Core Docker 이미지 안에서는 pip 로 설치 하시면 됩니다.


하기 가이드는 docker 내부 쉘에서 진행됩니다.


1. 일단 Yeelight Beside Lamp 의 주소를 찾아야겠죠?


root@aml:~# docker exec -it hass /bin/bash


bash-5.0# hciconfig

hci0:   Type: Primary  Bus: UART

        BD Address: AA:AA:AA:AA:AA:AA  ACL MTU: 1021:8  SCO MTU: 64:1

        UP RUNNING PSCAN

        RX bytes:4169178 acl:1128 sco:0 events:49289 errors:0

        TX bytes:293485 acl:1127 sco:0 commands:21983 errors:0


bash-5.0# hcitool lescan

LE Scan ...

F8:24:41:E0:92:12 XMCTD_9212

70:41:D9:98:90:37 (unknown)

70:41:D9:98:90:37 (unknown)

F8:24:41:E0:92:12 XMCTD_9212


2. yeelightbt 설치하기


bash-5.0# pip install git+https://github.com/rytilahti/python-yeelightbt/

Collecting git+https://github.com/rytilahti/python-yeelightbt/

  Cloning https://github.com/rytilahti/python-yeelightbt/ to /tmp/pip-req-build-dqsfcn7g

  Running command git clone -q https://github.com/rytilahti/python-yeelightbt/ /tmp/pip-req-build-dqsfcn7g

Requirement already satisfied (use --upgrade to upgrade): python-yeelightbt==0.0.4 from git+https://github.com/rytilahti/python-yeelightbt/ in /usr/local/lib/python3.8/site-packages

Requirement already satisfied: bluepy in /usr/local/lib/python3.8/site-packages (from python-yeelightbt==0.0.4) (1.3.0)

Requirement already satisfied: construct in /usr/local/lib/python3.8/site-packages (from python-yeelightbt==0.0.4) (2.9.45)

Requirement already satisfied: click in /usr/local/lib/python3.8/site-packages (from python-yeelightbt==0.0.4) (7.1.2)

Building wheels for collected packages: python-yeelightbt

  Building wheel for python-yeelightbt (setup.py) ... done

  Created wheel for python-yeelightbt: filename=python_yeelightbt-0.0.4-py3-none-any.whl size=11667 sha256=a30a0219b9c89079af88fb9fde3a0b89e8315b5f888196270a59e17c3a6d7760

  Stored in directory: /tmp/pip-ephem-wheel-cache-4c3m3cwq/wheels/a9/2e/1a/9d766b73af29410a2ff79dc367717751536ce3d1f1b2a48470

Successfully built python-yeelightbt


3. 모듈 사용하기


일단 램프의 전원을 뽑으시고, 전원버튼을 누른 상태에서 전원을 꼽습니다. (BT 초기화)



그리고 하기와 같이 진행합니다.


bash-5.0# export YEELIGHTBT_MAC=F8:24:41:E0:92:12


bash-5.0# yeelightbt
INFO:yeelightbt.connection:Requesting characteristics for uuid 8f65073d-9f57-4aaa-afea-397d19d5bbeb
INFO:yeelightbt.connection:Requesting characteristics for uuid aa7d3f34-2d4f-41e0-807f-52fbf8cf7443
Waiting for pairing, please push the button/change the brightness
Got notif: <Lamp F8:24:41:E0:92:12 is_on(True) mode(White) rgb((0, 0, 0, 0)) brightness(45) colortemp(4000)>
MAC: F8:24:41:E0:92:12
  Mode: White
  Color: (0, 0, 0, 0)
  Temperature: 4000
  Brightness: 45
We are paired.


페어링시에 불이 벌렁벌렁 거립니다. 그때 전원버튼 밑에 있는 작은 버튼을 눌러서 페어링을 해주시면 됩니다.


그럼 몇가지 커맨드를 보내봅니다.


bash-5.0# yeelightbt off

WARNING:yeelightbt.connection:Unable to connect to the device F8:24:41:E0:92:12, retrying: Failed to connect to peripheral F8:24:41:E0:92:12, addr type: public

INFO:yeelightbt.connection:Requesting characteristics for uuid 8f65073d-9f57-4aaa-afea-397d19d5bbeb

INFO:yeelightbt.connection:Requesting characteristics for uuid aa7d3f34-2d4f-41e0-807f-52fbf8cf7443

Got notif: <Lamp F8:24:41:E0:92:12 is_on(True) mode(White) rgb((0, 0, 0, 0)) brightness(50) colortemp(4000)>


bash-5.0# yeelightbt brightness 50
INFO:yeelightbt.connection:Requesting characteristics for uuid 8f65073d-9f57-4aaa-afea-397d19d5bbeb
INFO:yeelightbt.connection:Requesting characteristics for uuid aa7d3f34-2d4f-41e0-807f-52fbf8cf7443
Got notif: <Lamp F8:24:41:E0:92:12 is_on(True) mode(White) rgb((0, 0, 0, 0)) brightness(50) colortemp(4000)>
Setting brightness to 50

bash-5.0# yeelightbt temperature 2000
INFO:yeelightbt.connection:Requesting characteristics for uuid 8f65073d-9f57-4aaa-afea-397d19d5bbeb
INFO:yeelightbt.connection:Requesting characteristics for uuid aa7d3f34-2d4f-41e0-807f-52fbf8cf7443
Got notif: <Lamp F8:24:41:E0:92:12 is_on(True) mode(White) rgb((0, 0, 0, 0)) brightness(50) colortemp(4000)>
Setting the temperature to 2000 (brightness: None)


참고로 하기 에러가 많이 발생합니다. 이 부분은 RSSI 가 -70 정도가 되면 거의 발생하지 않지만, BLE 특성상 감도가 너무 낮아서, 안테나 튜닝을 하거나 해야합니다.


  File "/usr/local/lib/python3.8/site-packages/bluepy/btle.py", line 439, in _connect

    raise BTLEDisconnectError("Failed to connect to peripheral %s, addr type: %s" % (addr, addrType), rsp)

bluepy.btle.BTLEDisconnectError: Failed to connect to peripheral F8:24:41:E0:92:12, addr type: public


저는 하기와 같이 안테나 튜닝을 했네요...






4. bash 스크립트 짜기


일단은 상기 yeelightbt 의 실행 실패를 대비하고, 명령의 간소화를 위해

그리고 현 상태를 파악하기 위해 스크립트를 작성합니다.


https://github.com/djjproject/yeelightbt_script


스크립트 내용은 아래와 같습니다.


#!/bin/bash


export YEELIGHTBT_MAC=F8:24:41:E0:92:12 # 맥 주소 Export


# atomic operation 을 위한 부분입니다.

# mkdir 은 atomic operation 을 정확하게 만족합니다.

# 특히 ps -ef 로 돌고 있는지 체크가 가능하지만, 간혹 가다가 프로세스가 2개 이상 돌때가 있는데 그걸 방지합니다.

function runstate() { 

mkdir /config/yeelight.run

while [[ "$?" -gt 0 ]]

do

echo "lock fail!!! retrying..."

sleep 1

mkdir /config/yeelight.run

done

}


# 에러가 나면 retry 하는 스크립트 입니다.

function sendcommand() {

value=$(yeelightbt $1 $2)

while [ "$?" != "0" ]

do

echo "retry yeelight setting..."

    sleep 1

    value=$(yeelightbt $1 $2)

    done

case $1 in

brightness)

echo "$2" > /config/yeelight.bright

;;

temperature)

echo "$2" > /config/yeelight.temp

;;

*)

echo "$1" > /config/yeelight.power

;;

esac

rm -rf /config/yeelight.run

}


# 최초 명령 분기를 받는 부분입니다.

case $1 in


# 초기에는 값을 grep 으로 파싱했지만, 이전 state 를 리포팅해주어서 맞지가 않는 문제 때문에

# 커맨드에 성공하면, 그 값을 /config 에 저장합니다.

state)

power=$(cat /config/yeelight.power)

bright=$(cat /config/yeelight.bright)

temp=$(cat /config/yeelight.temp)


# 저장한 값을 json 형태로 print 합니다.

echo -e "{\n  \"power\": \"$power\",\n  \"bright\": \"$bright\",\n  \"temp\": \"$temp\"\n}"

;;


# power 컨트롤에 대한 부분입니다.

power)

runstate

if [ "$2" = "on" ]; then

sendcommand $2

elif [ "$2" = "off" ]; then

sendcommand $2

fi

;;

# 밝기에 대한 부분입니다.

bright)

runstate

sendcommand "brightness" "$2"

;;

# 온도에 대한 부분입니다.

# HA 에서는 template Light 플랫폼이 mired 를 사용해서 범위가 조금 안맞아서 6500 보다 넘어가면 yeelight 의 맥스값을 넣도록 작성했습니다.

temp)

runstate

temp_val=$2

if [ "$2" -gt 6500 ]; then

temp_val=6500

fi

sendcommand "temperature" "$temp_val"

;;

*)

;;

esac



exit 0


상기 코드가 몇줄 되지 않지만, 상당히 많은 부분을 고려해서 작성했습니다.


블루투스의 경우 한번에 한 세션이 들어와야해서 두 프로세스가 돌지 못하도록 막았습니다.


bluepy-helper 가 결국에는 2개 이상 돌지 못하도록 하는 것입니다.



그러나, 명령 커맨드가 쌓였을 경우 조금 문제가 있는 것이, 순서가 제대로 지켜지지 않는 것입니다. 그래서 자동화로 조금 손을 봅니다.


상기 스크립트는 docker 기준으로 /config/yeelight.sh 에 두시면 됩니다.


부수 파일로 yeelight.bright / yeelight.power / yeelight.temp 가 생깁니다. 이 부분은 현재 값을 저장하기 위한 파일


yeelight.run 폴더는 mkdir 의 atomic operation 을 활용한 두 프로세스가 돌지 않게 하기 위해 생성되는 빈 폴더 입니다.


bash-5.0# ls

automations.yaml          home-assistant_v2.db-shm  smb.conf

configuration.yaml        home-assistant_v2.db-wal  switchs.yaml

core                      input_boolean.yaml        tts

custom_components         kakao.session             www

deps                      known_devices.yaml        yeelight.bright

google_assistant.json     scenes.yaml               yeelight.power

groups.yaml               scripts.yaml              yeelight.sh

home-assistant.log        secrets.yaml              yeelight.temp

home-assistant_v2.db      sensors.yaml


특히, yeelight.run 폴더의 경우 HA 가 리스타트 하면 지워줘야 합니다. 이전에 무드등을 컨트롤 중에 HA 를 리스타트 하면, 폴더가 남아 있어서 프로세스가 sleep 1 로 무한정 대기를 하게 됩니다.





5. HA 와 본격 연동하기


일단 커멘드라인 센서를 생성합니다.


- platform: command_line

  name: besidelamp_state

  command: '/config/yeelight.sh state'

  value_template: '{{ value_json }}'

  json_attributes:

    - power

    - bright

    - temp

  command_timeout: 5

  scan_interval: 1


HA에 잘 올라오는지 체크합니다.



아무래도 느린 BT 반응을 개선하기 위해 스캔 인터벌을 1초로 둡니다.


상기 스크립트의 경우 scan 명령은 파일에 저장된 power bright temp 값을 cat 해서 전달하기 때문에 반응이 바로 바로 옵니다.


다음으로 스크립트 몇개를 생성합니다.


  lamp_turnon: '/config/yeelight.sh power on'

  lamp_turnoff: '/config/yeelight.sh power off'

  lamp_set_bright: '/config/yeelight.sh bright {{value}}'

  lamp_set_temp: '/config/yeelight.sh temp {{value}}'


이 부분으로 Template Light 를 생성합니다.


  - platform: template

    lights:

      beside_lamp:

        friendly_name: "무드등"


# 밝기의 부분인데 yeelight 에서는 0~100 으로 받지만, HA 에서는 0 ~ 255 로 받기 때문에 변환이 필요합니다.

        level_template: "{{ ((state_attr('sensor.besidelamp_state', 'bright') | float) * 255 / 100) | int }}"

        value_template: "{{ is_state_attr('sensor.besidelamp_state', 'power', 'on') }}"


# 온도의 부분도 mired 값에서 kelvin 값으로 변환이 필요합니다.

        temperature_template: "{{ (1000000 / (state_attr('sensor.besidelamp_state', 'temp') | float)) |  int }}"

        turn_on:

          service: shell_command.lamp_turnon

        turn_off:

          service: shell_command.lamp_turnoff

        set_level:

          service: shell_command.lamp_set_bright

          data_template:


# 상기에 밝기 값 변환 부분입니다.

            value: "{{ ((brightness | float / 255) * 100) | int }}"

        set_temperature:

          service: shell_command.lamp_set_temp

          data_template:

# 상기의 mired kelvin 변환 부분입니다.

            value: "{{ (1000000 / (color_temp | float)) | int }}"


상기를 코멘트를 빼고 첨부드립니다.


  - platform: template

    lights:

      beside_lamp:

        friendly_name: "무드등"

        level_template: "{{ ((state_attr('sensor.besidelamp_state', 'bright') | float) * 255 / 100) | int }}"

        value_template: "{{ is_state_attr('sensor.besidelamp_state', 'power', 'on') }}"

        temperature_template: "{{ (1000000 / (state_attr('sensor.besidelamp_state', 'temp') | float)) |  int }}"

        turn_on:

          service: shell_command.lamp_turnon

        turn_off:

          service: shell_command.lamp_turnoff

        set_level:

          service: shell_command.lamp_set_bright

          data_template:

            value: "{{ ((brightness | float / 255) * 100) | int }}"

        set_temperature:

          service: shell_command.lamp_set_temp

          data_template:

            value: "{{ (1000000 / (color_temp | float)) | int }}"


그러면 대충 아래의 그림으로 연동됩니다.




실제로 컬러도 변경이 가능하나, 컬러 넣다가 스트레스 받을 것 같아서 밝기와 색온도만 넣었습니다.


대충 색온도로 원하는 컬러가 구현이 가능해서 그냥 색깔 선택은 제외 하였습니다.


영상으로 아래와 같습니다. 하기 영상은 안테나 튜닝을 하기 전에 영상으로 조금 딜레이가 큽니다.



사진으로는 아래와 같습니다.






6. 자동화 작성하기


자동화를 하기와 같이 작성합니다.


거실등이 꺼질때 / 해가 지평선 아래일 때 / 무드등 상태가 바뀔때


해가 지평선 아래인지 한번더 체크

집에 제가 있는지 체크 (재실모드)

거실등이 꺼져있을때

무드등이 꺼져있을때


무드등 밝기를 조금 밝게 유지

30분 후

따뜻한 색감으로 어둡게 유지


- id: '1595750364026'

  alias: besidelamp night on

  description: ''

  trigger:

  - entity_id: light.geosildeung

    from: 'on'

    platform: state

    to: 'off'

  - entity_id: sun.sun

    platform: state

    to: below_horizon

  - entity_id: light.beside_lamp

    platform: state

  condition:

  - condition: and

    conditions:

    - condition: state

      entity_id: sun.sun

      state: below_horizon

    - condition: state

      entity_id: input_boolean.home_state

      state: 'on'

    - condition: state

      entity_id: light.geosildeung

      state: 'off'

    - condition: state

      entity_id: light.beside_lamp

      state: 'off'

  action:

  - data:

      color_temp: '200'

    entity_id: light.beside_lamp

    service: light.turn_on

  - data:

      brightness: '100'

    entity_id: light.beside_lamp

    service: light.turn_on

  - data: {}

    entity_id: light.beside_lamp

    service: light.turn_on

  - delay: 00:30:00

  - data:

      color_temp: '500'

    entity_id: light.beside_lamp

    service: light.turn_on

  - data:

      brightness: '10'

    entity_id: light.beside_lamp

    service: light.turn_on

  mode: restart

  max: 10



- id: '1595750873395'
  alias: besidelamp night off
  description: ''
  trigger:
  - entity_id: light.geosildeung
    for: 00:05:00
    from: 'off'
    platform: state
    to: 'on'
  - entity_id: sun.sun
    platform: state
    to: above_horizon
  - entity_id: light.beside_lamp
    platform: state
  condition:
  - condition: and
    conditions:
    - condition: state
      entity_id: light.beside_lamp
      state: 'on'
  action:
  - data: {}
    entity_id: light.beside_lamp
    service: light.turn_off
  mode: restart


꺼지는 자동화의 경우


거실등이 켜졌고 5분동안 지속되면 / 해가 지평선 위일때 / 무드등 상태가 바뀔때


무드등이 켜져 있으면


끈다


이런식으로 타이트하게 자동화를 작성합니다.


몇일 이렇게 살아보니 크게 문제가 없어서 ... 문제가 없고 좋은데요. 일단 하기의 문제들이 조금 있습니다.





7. 문제점


1. 태스크가 대기할 때 순서가 맞지 않음


bash 쉘 스크립트로 실행 순서를 지켜주기가 어렵습니다.

그래서 자동화를 더 더욱 빡세게 조정하게 되네요.


Every 0.5s: ps -ef                                                                 2020-08-02 16:38:13


PID   USER     TIME  COMMAND

    1 root      0:00 s6-svscan -t0 /var/run/s6/services

   35 root      0:00 s6-supervise s6-fdholderd

  190 root      0:01 udevd --daemon

  228 root      0:00 s6-supervise home-assistant

  231 root      4h36 python3 -m homeassistant --config /config

 3209 root      0:00 /bin/bash

 3237 root      0:00 watch -n 0.5 ps -ef

 4078 root      0:00 {yeelight.sh} /bin/bash /config/yeelight.sh temp 2493

 4217 root      0:00 {yeelight.sh} /bin/bash /config/yeelight.sh temp 2493

 4218 root      0:00 {yeelightbt} /usr/local/bin/python /usr/local/bin/yeelightbt temperature 2493

 4219 root      0:00 grep is_on

 4228 root      0:00 /usr/local/lib/python3.8/site-packages/bluepy/bluepy-helper

 4256 root      0:00 {yeelight.sh} /bin/bash /config/yeelight.sh bright 92

 4288 root      0:00 {yeelight.sh} /bin/bash /config/yeelight.sh temp 2164

 4307 root      0:00 udevd --daemon

 4322 root      0:00 sleep 1

 4327 root      0:00 sleep 1

 4330 root      0:00 ps -ef


이런식으로 HA 에서 커맨드가 여러개 들어오면, HA 에서 실행한대로 순서가 유지가 되지 않습니다.


2. 반응이 너무 느림


흠.. 이 문제는 daemon 형태로 python 을 작성하여, BLE 연결이 계속 유지되도록 하는 방법밖에 없는데..

일단은 능력 부족이네요..





8. 마치며...


대략적으로 자동화를 해 두고, 전등을 직접 켜는 일이 없어서


이렇게 만족하고 넘어갑니다만, 다음에 너무 불편하면 daemon 을 작성하는 게시글로 뵙겠습니다.


감사합니다.


반응형

댓글