다원 와이파이 플러그 HA 연동의 콤필레이션 (CSS님 사설서버 + HA 브릿지)
안녕하세요? 도정진입니다.
아 많은 시간이 흘렀습니다.
0편 : stkang90님 로컬 연동
https://blog.djjproject.com/668
1편 : 설치기
https://blog.djjproject.com/798
2편 : 컨테이너 만들고 배포
https://blog.djjproject.com/807
3편 : HA에 MQTT 디스커버리로 연동하기
--> 본문
1. CSS님 파워매니서 사설 서버 소스
설치 문서 : https://github.com/SeongSikChae/PowerManagerDocument
서버 소스 : https://github.com/SeongSikChae/PowerManagerServerV2
플러그 연동 소스 : https://github.com/SeongSikChae/PowerManagerConfig
2. 컨테이너 및 브릿지 소스
컨테이너 소스 : https://github.com/djjproject/dawon_css_server_docker
docker hub : https://hub.docker.com/r/djjproject/dawon_css_server
브릿지 소스 : https://github.com/djjproject/dawon_css_server_ha_bridge
3. 브릿지 설정 - CSS님 서버에서 더미 플러그 만들기
아래와 같이 더미 플러그를 하나 생성합니다.
이 더미플러그를 통해 CSS님 서버에 접속하여 플러그 메시지를 가져옵니다.
이때 서버 접속은 아래와 같이 이루어 집니다.
ClientID : DAWONDNS-habridge0000
User : DAWONDNS-habridge-habridge0000
Password : (위의 Password 입력값)
4. 컨테이너의 macvlan 을 호스트 네트워크에 연동
macvlan 네트워크의 경우 호스트에서 접근하지 못하는 문제가 있습니다.
아래의 글을 참고하여 네트워크를 수정합니다.
https://blog.djjproject.com/797
# 저는 이더넷 이름이 vmbr0 입니다. (eth0 enpXsX 일 수도 있습니다.) root@debian:~# ip link add macvlan-shim link vmbr0 type macvlan mode bridge # 네트워크 대역에서 빈 곳을 하나 지정해서 ip 추가해줍니다. root@debian:~# ip addr add 192.168.0.234/24 dev macvlan-shim # 인터페이스를 up 시켜줍니다. root@debian:~# ip link set macvlan-shim up # 파워매니저 서버가 돌고있는 ip를 route 추가 해줍니다. root@debian:~# ip route add 192.168.0.200 dev macvlan-shim |
일단 192.168.0.200 이 호스트에서 연결이 가능한지 확인합니다.
인증서가 등록이 되어 있지 않아 에러가 나지만 정상 연결 됩니다.
root@debian:~# curl https://192.168.0.200 curl: (60) SSL certificate problem: unable to get local issuer certificate More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. |
그리고 위의 스크립트를 init 스크립트나 rc.local 에 등록합니다.
nmcli 의 post-up-d 를 통해 등록할 수 있지만 귀찮아서 init 스크립트에 등록합니다.
root@debian:~# cat /opt/scripts/init.sh # macvlan powermanager ip link add macvlan-shim link vmbr0 type macvlan mode bridge ip addr add 192.168.0.234/24 dev macvlan-shim ip link set macvlan-shim up ip route add 192.168.0.200 dev macvlan-shim |
5. HA의 MQTT 설정 변경
HomeAssistant 와 연동된 mqtt 설정파일을 엽니다.
아래와 같이 기입합니다.
root@debian:~# cat /etc/mosquitto/mosquitto.conf # powermanager MQTT connection dawon-bridge address 192.168.0.200:1803 topic # both 0 dawon/ dwd.v1/ clientid DAWONDNS-habridge0000 remote_username DAWONDNS-habridge-habridge0000 remote_password djjproject cleansession true try_private false |
특히 topic 부분이 중요합니다.
CSS님 서버의 MQTT 는 dwd.v1 의 메시지를 사용하며 HA의 MQTT에서는 dawon 으로 바뀌어 보이도록 하는 설정입니다.
여러 장소의 MQTT 를 연동할 때 이렇게 설정하면 패킷이 중복이되거나 하는 부분을 해결하실 수 있습니다.
서비스를 재시작 합니다.
root@debian:~# systemctl restart mosquitto |
6. 연동 확인하기
저는 HA가 쓰는 mqtt 에 인증이 없습니다.
인증이 있다면, -u -P 옵션을 주셔서 아이디 비번을 기입해 주세요.
root@debian:~# mosquitto_sub -t "dawon/#" { "sid":"2", "msg":{ "o":"n", "e":[ { "n":"/100/0/31", "sv":"false", "ti":"1660710384" }, { "n":"/100/0/1", "sv":"229.29", "ti":"1660710384" }, { "n":"/100/0/11", "sv":"0.00", "ti":"1660710384" }, { "n":"/100/0/4", "sv":"40.96", "ti":"1660710384" } ] } } { |
7. 브릿지 설치하기
아래와 같이 설치를 진행합니다.
(파이썬 개초보라 코드가 좀 효율적이지 않습니다.)
root@debian:~# mkdir -p /opt/dawon-bridge root@debian:/opt/dawon-bridge# git clone https://github.com/djjproject/dawon_css_server_ha_bridge . root@debian:/opt/dawon-bridge# cp dawon-bridge.service /etc/systemd/system root@debian:/opt/dawon-bridge# systemctl enable dawon-bridge |
bridge.py 를 열어 설정을 진행합니다.
root@debian:/opt/dawon-bridge# vi bridge.py mqtt_clientid = 'habridge' mqtt_username = '' mqtt_password = '' mqtt_host = '127.0.0.1' mqtt_port = '1883' mqtt_topic = 'dawon' mqtt_ha_topic = 'dawon-switch' |
따로 HA의 mqtt 에 비번이 없으시면 그냥 진행하셔도 됩니다.
아래의 명령으로 서비스를 시작합니다.
root@debian:/opt/dawon-bridge# systemctl start dawon-bridge root@debian:/opt/dawon-bridge# systemctl status dawon-bridge ● dawon-bridge.service - Dawon Plug CSS HomeAssistant Bridge Loaded: loaded (/etc/systemd/system/dawon-bridge.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2022-08-17 13:34:16 KST; 1min 8s ago Main PID: 8567 (python3) Tasks: 1 (limit: 4915) Memory: 8.7M CGroup: /system.slice/dawon-bridge.service └─8567 /usr/bin/python3 /opt/dawon-bridge/bridge.py Aug 17 13:34:16 debian systemd[1]: Started Dawon Plug CSS HomeAssistant Bridge. |
8. 확인하기
2개를 연동하였는데 2개가 discovery 되어 있습니다.
구버전과 신버전 모두 연동이 된 상태입니다.
Discovery 의 경우 브릿지 서비스가 시작될 때, CSS님 서버에서 오는 메시지를 분석하여 동적으로 전송됩니다.
브릿지 서비스가 동작 중일 때, 이 플러그가 Discovery 메시지를 보냈는지 보내지 않았는지 변수를 가지고 있어, 중복으로 전송되지는 않습니다.
ON/OFF 의 경우 homeassistant/dawon-switch/DAWONDNS-[DEVICETYPE]-[DEVICEMAC]/set 의 토픽을 청취하고 있다가 ON / OFF 가 오면 브릿지 서비스에서 메시지를 발행합니다.
9. 코드 설명
파이썬을 잘 작성하지 못해 비효율이 있을 수 있고, 멀티 쓰레드나 큐를 등록하지 않고 작성했기 때문에 플러그 수량이 많으면 메시지들이 씹힐 수 있습니다.
기본적으로 모든 메시지를 청취하며, 특정 메시지가 올 때 콜백함수를 등록했습니다.
if __name__ == "__main__": mqtt_ret = -1 while True: client = connect_mqtt(mqtt_clientid, mqtt_username, mqtt_password, mqtt_host, mqtt_port) # dawon/+/iot-server/notify/json 의 메시지가 올 경우 처리할 함수 등록 client.message_callback_add(mqtt_topic + "/+/iot-server/notify/json", on_message_sensor_value) # homeassistant/dawon-switch/+/set 의 메시지가 올 경우 처리할 함수 등록 client.message_callback_add("homeassistant/" + mqtt_ha_topic + "/+/set", on_message_control_value) # 기본적으로 모든 메시지를 청취합니다. client.subscribe("#") client.loop_forever() |
on_message_sensor_value 함수입니다.
CSS님 서버에서 올라오는 값 중에 플러그가 리포팅 하는 부분입니다.
def on_message_sensor_value(client, userdata, msg): # 토픽을 짤라서 1번째가 DAWONDNS-B5X-12345678 입니다. device_name = msg.topic.split("/")[1] device_data = json.loads(msg.payload.decode()) # 플러그 정보를 HA 로 publish 하는 함수입니다. publish_discovery() # 페이로드 메시지에서 e 의 json 값에서 n값과 sv 값이 스위치 상태 값입니다. for data in device_data["msg"]["e"]: parse_value(data["n"], data["sv"]) |
먼저 publish 함수를 보면, 특정 list 에 없으면 넣고 publish 를 하게 되어 있습니다.
def publish_discovery(): if not device_name in plug_list: plug_list.append(device_name) print(f"Register {device_name}.") |
publish 시에 아래의 방법으로 push 합니다.
# ha_manufacture : DAWONDNS ha_manufacture = device_name.split("-")[0] # ha_device_mac : 12345678 ha_device_mac = device_name.split("-")[2] ha_version_data = { "name": ha_device_mac + "_Version", #"object_id": ha_device_mac + "_Version", "unique_id": ha_device_mac + "_Version", "device": { "identifiers": ha_device_mac, "name": device_name, "manufacturer": ha_manufacture, }, "state_topic": "homeassistant/sensor/" + device_name + "-version/state", "value_template": "{{ value_json.Version }}", } # 각각 정보를 push 합니다. # HA 에서는 device 값을 기반으로 멀티 펑션 디바이스를 생성할 수 있습니다. # 이때는 retain 값이 True 로 메시지가 발행됩니다. client.publish("homeassistant/sensor/" + device_name + "-version/config", json.dumps(ha_version_data), 2, True) client.publish("homeassistant/sensor/" + device_name + "-address/config", json.dumps(ha_address_data), 2, True) client.publish("homeassistant/sensor/" + device_name + "-voltage/config", json.dumps(ha_voltage_data), 2, True) client.publish("homeassistant/sensor/" + device_name + "-power/config", json.dumps(ha_power_data), 2, True) client.publish("homeassistant/sensor/" + device_name + "-temperature/config", json.dumps(ha_temperature_data), 2, True) client.publish("homeassistant/switch/" + device_name + "/config", json.dumps(ha_switch_data), 2, True) |
다음은 센서값을 publish 하는 부분입니다.
parse_value 함수는 이렇게 불립니다.
실제 데이터는 이렇게 오게 됩니다.
{ "sid": "2", "msg": { "o": "n", "e": [ # 1번째 { "n": "/100/0/40", "sv": "false", "ti": "1660711661" }, # 2번째 { "n": "/100/0/41", "sv": "0.00", "ti": "1660711661" }, # 3번째 { "n": "/100/0/42", "sv": "120", "ti": "1660711661" } ] } } |
# json 데이터가 여러개 올 수 있기 때문에 for 문을 돌면서 확인합니다. for data in device_data["msg"]["e"]: parse_value(data["n"], data["sv"]) |
def parse_value(data_type, value): # 각 n 의 값에 따라 sv 값을 넘겨줍니다. switch_func = { "/3/0/3" : publish_version, # 버전정보 "/4/0/4" : publish_address, # 아이피 주소 정보 "/100/0/1" : publish_voltage, # 전압 정보 "/100/0/11" : publish_power, # 전력량 정보 "/100/0/4" : publish_temperature, # 온도 정보 "/100/0/31" : publish_state } # ON/OFF 상태 정보 try: switch_func[data_type](value) except Exception as e: pass |
간단하게 publish_state 함수만 보면 아래와 같습니다.
def publish_state(value): # value 값에 true / false 가 들어오면 그에 따라 ON OFF 메시지를 publish 합니다. client.publish("homeassistant/" + mqtt_ha_topic + "/" + device_name + "/state", "ON" if value == "true" else "OFF" , 0, False) |
센서 정보의 경우 W값을 보면 아래와 같습니다.
def publish_power(value): # value 를 Power 로 놓고 json 메시지를 publish 합니다. publish_data = { "Power": value } client.publish("homeassistant/sensor/" + device_name + "-power/state", json.dumps(publish_data), 0, False) |
다음으로 HomeAssistant 에서 플러그를 컨트롤 할 때 처리하는 부분입니다.
def on_message_control_value(client, userdata, msg): device_name = msg.topic.split("/")[2] # 먼저 plug_list 변수에 등록된 플러그인지 검색합니다. 이는 on_message_sensor_value 함수에서 처리 되었습니다. if device_name in plug_list: print(f"Match device: {device_name}:" + msg.payload.decode()) # 시간 값을 초 단위로 계산합니다. time_tick = int(time.time()) send_data = { "sid": "2", "msg": { "o": "e", "e": [ # ON 이면 true 로 OFF 면 false 로 메시지를 발행합니다. {"n": "/100/0/31", "sv": "true" if msg.payload.decode() == "ON" else "false", "ti": time_tick} ] } } client.publish(mqtt_topic + "/iot-server/" + device_name + "/execute/json", json.dumps(send_data), 2, False) else: print(f"Not registered device: {device_name}") |
10. 마치며..
현재 아래와 같이 연동된 플러그가 MQTT 자동 Discovery 로 자동으로 HA와 연동 됩니다.
연동 이후 반응 속도는 아래와 같습니다.
감사합니다.
마치겠습니다.