IoT

다원 와이파이 플러그 HA 연동의 콤필레이션 (CSS님 사설서버 + HA 브릿지)

ㅋㅋ잠자 2022. 8. 17. 14:00
반응형

안녕하세요? 도정진입니다.

 

 

아 많은 시간이 흘렀습니다.

 

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와 연동 됩니다.

연동 이후 반응 속도는 아래와 같습니다.

 

감사합니다.

마치겠습니다.

반응형