HomeAssistant 샤오미 이라이트 무드등 BLE 연동하기
안녕하세요?
최근에 무드등이 있으면 좋을 것 같아서 중고나라에서 하나 샀습니다. 가격이 매우 싸서 하나 물었는데요.
알고보니, 이게 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 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)>
참고로 하기 에러가 많이 발생합니다. 이 부분은 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
꺼지는 자동화의 경우
거실등이 켜졌고 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 을 작성하는 게시글로 뵙겠습니다.
감사합니다.