본문 바로가기
스마트폰 (Mobile)

LG 에어컨 MQTT 브로커 통해서 홈어시스턴트 연동 자동화 스마트홈

by 날으는물고기 2024. 10. 20.

LG 에어컨 MQTT 브로커 통해서 홈어시스턴트 연동 자동화 스마트홈

Home Assistant에서 MQTT를 사용하여 LG 에어컨을 제어하기 위해, MQTT 브로커를 설정하고, 해당 브로커와 통신할 수 있는 코드를 작성해야 합니다. MQTT 브로커로는 일반적으로 Mosquitto를 많이 사용합니다. 아래는 이를 설정하고 사용하는 방법에 대한 단계별 가이드입니다.

  1. MQTT 브로커 설치 및 설정 (Mosquitto 예시)
    sudo apt-get update
    sudo apt-get install mosquitto mosquitto-clients
  2. Home Assistant에 MQTT 통합 설정
    Home Assistant 설정 파일 (configuration.yaml)에 MQTT 브로커 설정을 추가합니다.
    mqtt:
      broker: your_broker_ip
      port: 1883
      username: your_username
      password: your_password
  3. Python 스크립트를 통해 MQTT 메시지 전송 및 수신
    아래는 기존의 TCP 소켓 통신 코드를 MQTT를 사용하도록 수정한 Python 코드입니다.
    import paho.mqtt.client as mqtt
    import sys
    import binascii
    
    mqtt_broker = 'your_broker_ip'
    mqtt_port = 1883
    mqtt_topic = 'home/lg_airconditioner/command'
    mqtt_response_topic = 'home/lg_airconditioner/response'
    
    packet = '8000A3' + sys.argv[1]
    
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("Connected to MQTT Broker!")
        else:
            print("Failed to connect, return code %d\n", rc)
    
    def on_message(client, userdata, msg):
        print("Received message: ", binascii.hexlify(msg.payload).decode())
        client.disconnect()
    
    client = mqtt.Client()
    client.username_pw_set('your_username', 'your_password')
    client.on_connect = on_connect
    client.on_message = on_message
    
    client.connect(mqtt_broker, mqtt_port, 60)
    
    client.loop_start()
    
    client.subscribe(mqtt_response_topic)
    client.publish(mqtt_topic, bytes.fromhex(packet))
    
    try:
        client.loop_forever()
    except KeyboardInterrupt:
        client.disconnect()
  4. Home Assistant 자동화 설정
    Home Assistant에서 MQTT 메시지를 수신하고, 에어컨 제어 명령을 처리하도록 자동화를 설정합니다.
    automation:
      - alias: 'LG Air Conditioner Command'
        trigger:
          platform: mqtt
          topic: 'home/lg_airconditioner/command'
        action:
          - service: script.lg_airconditioner_control
            data_template:
              command: '{{ trigger.payload }}'
    
    script:
      lg_airconditioner_control:
        sequence:
          - service: mqtt.publish
            data_template:
              topic: 'home/lg_airconditioner/response'
              payload: '{{ response_payload }}'
  5. Home Assistant 커스텀 스크립트 작성
    command 변수를 처리하여 에어컨 제어 명령을 수행하는 스크립트를 작성합니다.
    import paho.mqtt.publish as publish
    
    def handle_command(command):
        # 에어컨 제어 로직을 여기에 구현
        # 예시로 단순히 받은 명령을 그대로 응답하는 로직
        response_payload = command
    
        publish.single('home/lg_airconditioner/response', payload=response_payload, hostname='your_broker_ip')
    
    if __name__ == '__main__':
        import sys
        command = sys.argv[1]
        handle_command(command)

위 단계를 따라 설정을 완료하면, Home Assistant를 통해 LG 에어컨을 MQTT로 제어할 수 있습니다.

1. 요구 사항 이해

  • lgac/state/01부터 lgac/state/04까지의 MQTT 토픽에서 메시지를 수신하고, 이를 변환하여 다른 토픽으로 전송하는 시스템을 구축해야 합니다.
  • 이전에 수신된 값을 기억하고, 값이 변경되었을 때만 변환 및 전송하도록 설계해야 합니다.
  • 변환 과정에서 발생할 수 있는 오류를 처리하고, 시스템이 종료되지 않도록 예외 처리를 추가해야 합니다.
  • Home Assistant의 MQTT Discovery 기능을 활용하여 자동으로 센서를 등록하는 기능을 구현해야 합니다.
  • 로깅을 설정하여 시스템의 동작을 모니터링할 수 있도록 해야 합니다.

2. 기능별 설계

  • MQTT 클라이언트 초기화
    • MQTT 브로커에 연결하고, 필요한 토픽을 구독합니다.
    • 메시지 수신 시 호출되는 콜백 함수들을 설정합니다.
  • 이전 값 기억
    • 각 토픽에 대해 이전에 수신한 값을 기억하기 위해 딕셔너리를 사용합니다.
    • 새로운 값이 수신되면 이전 값과 비교하여 변경된 경우에만 메시지를 변환하고 전송하도록 합니다.
  • 메시지 변환 및 전송
    • 수신된 메시지를 16진수 문자열로 변환합니다.
    • 변환된 메시지를 필요한 토픽으로 전송합니다.
  • 예외 처리
    • 메시지 변환 과정에서 발생할 수 있는 오류를 처리하기 위해 예외 처리를 추가합니다.
    • 오류가 발생하더라도 시스템이 종료되지 않도록 합니다.
  • Home Assistant 통합
    • Home Assistant의 MQTT Discovery 기능을 활용하여 센서를 자동으로 등록합니다.
    • 초기 실행 시 각 센서에 대한 설정 메시지를 Home Assistant로 전송하여 센서를 등록합니다.
  • 로깅 설정
    • 시스템의 동작 상태를 모니터링할 수 있도록 로깅을 설정합니다.
    • 로그 파일은 일정 주기로 회전되도록 설정하고, 로그는 콘솔에도 출력되도록 합니다.

3. 구현 순서

  • 로깅 설정
    • 로그 파일 경로와 포맷터를 설정하고, 로그 파일을 일정 주기로 회전하도록 TimedRotatingFileHandler를 사용하여 설정합니다.
  • MQTT 클라이언트 초기화
    • MQTT 브로커에 연결하고, 필요한 토픽을 구독합니다.
    • 메시지 수신 시 호출될 콜백 함수들을 설정합니다.
  • 이전 값 기억 로직 추가
    • last_values 딕셔너리를 사용하여 각 토픽의 최근 값을 기억하도록 합니다.
    • 새로운 값이 수신될 때 이전 값과 비교하여 값이 변경된 경우에만 변환 및 전송이 이루어지도록 합니다.
  • 메시지 변환 및 전송
    • 수신된 메시지를 16진수 문자열로 변환하고, 변환된 메시지를 필요한 토픽으로 전송하는 로직을 구현합니다.
  • 예외 처리 추가
    • 메시지 변환 과정에서 발생할 수 있는 오류를 처리하는 예외 처리 로직을 추가하여 시스템이 종료되지 않도록 합니다.
  • Home Assistant 통합
    • Home Assistant의 MQTT Discovery 기능을 사용하여 센서를 자동으로 등록하는 기능을 구현합니다.
    • 초기 실행 시 필요한 설정 메시지를 Home Assistant로 전송하여 센서를 등록합니다.

4. 테스트 및 디버깅

  • 구현된 코드를 테스트하여 모든 기능이 정상적으로 작동하는지 확인합니다.
  • 메시지 변환 및 전송, 값의 변경 여부 판단, Home Assistant와의 통합, 예외 처리, 로깅 등의 기능을 테스트합니다.
  • 필요에 따라 디버깅을 통해 오류를 수정하고, 로깅을 통해 시스템의 동작을 모니터링합니다.

5. 최종 검토 및 배포

  • 모든 기능이 정상적으로 동작하는 것을 확인한 후, 코드를 최종 검토합니다.
  • 배포 환경에서 시스템을 실행하고, 로그를 통해 시스템이 정상적으로 작동하는지 지속적으로 모니터링합니다.
import paho.mqtt.client as mqtt
import binascii
import logging
import os
from logging.handlers import TimedRotatingFileHandler
import json

# 로깅 설정
logger = logging.getLogger('lgac_forward')
logger.setLevel(logging.DEBUG)

# 현재 파이썬 파일의 절대 경로를 기준으로 로그 파일 경로 설정
current_file_path = os.path.abspath(__file__)
current_dir = os.path.dirname(current_file_path)
log_dir = os.path.join(current_dir, 'log')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'lgac_forward.log')

# 파일 핸들러와 포매터 설정
formatter = logging.Formatter(fmt="%(asctime)s %(levelname)-8s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
file_handler = TimedRotatingFileHandler(log_file, when="midnight", backupCount=7)
file_handler.setFormatter(formatter)
file_handler.suffix = "%Y%m%d"
logger.addHandler(file_handler)

# 콘솔 핸들러 설정 (필요할 경우)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

# 바이너리 데이터를 16진수 문자열로 변환하는 함수
def binary_to_hex(packet):
    return binascii.hexlify(packet).decode('utf-8')

# 16진수 문자열을 바이너리 데이터로 변환하는 함수
def hex_to_binary(hex_str):
    try:
        return binascii.unhexlify(hex_str)
    except binascii.Error as e:
        logger.error(f"Error converting hex to binary: {e}")
        return None

# 최근 값을 저장할 딕셔너리
last_values = {
    "lgac/state/01": None,
    "lgac/state/02": None,
    "lgac/state/03": None,
    "lgac/state/04": None,
}

# MQTT 메시지를 수신할 때 호출되는 콜백 함수 (ew11b/recv)
def on_message_recv(client, userdata, msg):
    logger.debug(f"Received message on topic {msg.topic}:")
    logger.debug(f"Raw binary data: {msg.payload}")
    hex_data = binary_to_hex(msg.payload)
    logger.debug(f"Converted Hex data: {hex_data}")

    if len(hex_data) < 32:
        logger.error("Error: Hex data is less than 32 characters.")
        return
    elif len(hex_data) > 32:
        hex_data = hex_data[:32]

    sequence_number = hex_data[8:10]
    state_topic = f"lgac/state/{sequence_number}"

    # 값이 변경된 경우에만 전송
    if last_values.get(state_topic) != hex_data:
        last_values[state_topic] = hex_data
        logger.debug(f"Publishing to topic: {state_topic}")
        client.publish(state_topic, hex_data)
        logger.debug(f"Published converted message to {state_topic}")

# MQTT 메시지를 수신할 때 호출되는 콜백 함수 (lgac/send)
def on_message_send(client, userdata, msg):
    logger.debug(f"Received message on topic {msg.topic}:")
    hex_data = msg.payload.decode('utf-8')
    logger.debug(f"Received Hex data: {hex_data}")

    binary_data = hex_to_binary(hex_data)
    if binary_data is None:
        logger.error("Error: Failed to convert hex data to binary.")
        return

    logger.debug(f"Converted Binary data: {binary_data}")

    # 변환된 메시지를 다른 토픽으로 전송
    client.publish("ew11b/send", binary_data)
    logger.debug(f"Published converted message to ew11b/send")

# MQTT 클라이언트 초기화 함수
def init_connect():
    client = mqtt.Client()

    # 콜백 함수 설정
    client.message_callback_add("ew11b/recv", on_message_recv)
    client.message_callback_add("lgac/send", on_message_send)

    client.connect("your_broker_address")  # MQTT 브로커 주소 입력
    client.subscribe("ew11b/recv")
    client.subscribe("lgac/send")

    return client

# Home Assistant에 MQTT Discovery 메시지 전송 함수
def send_homeassistant_discovery(client):
    discovery_prefix = "homeassistant"
    sensors = [
        {"topic": "lgac/state/01", "sensor_id": "lgac_01_state"},
        {"topic": "lgac/state/02", "sensor_id": "lgac_02_state"},
        {"topic": "lgac/state/03", "sensor_id": "lgac_03_state"},
        {"topic": "lgac/state/04", "sensor_id": "lgac_04_state"},
    ]

    for sensor in sensors:
        config_topic = f"{discovery_prefix}/sensor/{sensor['sensor_id']}/config"
        config_payload = {
            "name": sensor['sensor_id'],
            "state_topic": sensor['topic'],
            "unique_id": sensor['sensor_id'],
            "device": {
                "identifiers": [sensor['sensor_id']],
                "name": sensor['sensor_id'],
                "model": "LGAC",
                "manufacturer": "LG"
            }
        }
        client.publish(config_topic, json.dumps(config_payload), retain=True)
        logger.debug(f"Published Home Assistant discovery message for {sensor['sensor_id']}")

# 메인 함수
if __name__ == "__main__":
    client = init_connect()
    send_homeassistant_discovery(client)  # Home Assistant MQTT Discovery 메시지 전송
    try:
        client.loop_forever()
    except Exception as e:
        logger.exception("Daemon finished!")

Home Assistant에서 4개의 센서를 5분 단위로 반복해서 실행하는 자동화를 설정하려면, time_pattern 트리거와 for 루프를 사용하여 각 센서를 개별적으로 처리할 수 있습니다. 그러나 Home Assistant 자동화 YAML에서는 직접적인 반복문을 사용할 수 없으므로, 여러 자동화를 설정하거나 Python 스크립트를 사용하여 반복적인 작업을 수행할 수 있습니다.

1. Home Assistant 자동화 설정

다음은 time_pattern 트리거를 사용하여 5분마다 자동화가 실행되도록 설정하는 예제입니다.

automation:
  - alias: "LGAC 01-04 Command Send Every 5 Minutes"
    trigger:
      - platform: time_pattern
        minutes: "/5"
    action:
      - service: mqtt.publish
        data:
          topic: "home/lgac/send"
          payload_template: >
            {{ '8000A3' + state_attr('sensor.lgac_01_state', 'operscan') + state_attr('sensor.lgac_01_state', 'opermode') + state_attr('sensor.lgac_01_state', 'temperature') + state_attr('sensor.lgac_01_state', 'checksumscan') + (states('binary_sensor.ew11b_01') if is_state_attr('sensor.ew11b_01_status', 'value', 'true') else '') }}
      - service: mqtt.publish
        data:
          topic: "home/lgac/send"
          payload_template: >
            {{ '8000A3' + state_attr('sensor.lgac_02_state', 'operscan') + state_attr('sensor.lgac_02_state', 'opermode') + state_attr('sensor.lgac_02_state', 'temperature') + state_attr('sensor.lgac_02_state', 'checksumscan') + (states('binary_sensor.ew11b_02') if is_state_attr('sensor.ew11b_02_status', 'value', 'true') else '') }}
      - service: mqtt.publish
        data:
          topic: "home/lgac/send"
          payload_template: >
            {{ '8000A3' + state_attr('sensor.lgac_03_state', 'operscan') + state_attr('sensor.lgac_03_state', 'opermode') + state_attr('sensor.lgac_03_state', 'temperature') + state_attr('sensor.lgac_03_state', 'checksumscan') + (states('binary_sensor.ew11b_03') if is_state_attr('sensor.ew11b_03_status', 'value', 'true') else '') }}
      - service: mqtt.publish
        data:
          topic: "home/lgac/send"
          payload_template: >
            {{ '8000A3' + state_attr('sensor.lgac_04_state', 'operscan') + state_attr('sensor.lgac_04_state', 'opermode') + state_attr('sensor.lgac_04_state', 'temperature') + state_attr('sensor.lgac_04_state', 'checksumscan') + (states('binary_sensor.ew11b_04') if is_state_attr('sensor.ew11b_04_status', 'value', 'true') else '') }}
  • 트리거: time_pattern 트리거를 사용하여 매 5분마다 자동화를 실행합니다.
  • 작업: 각 센서(lgac_01에서 lgac_04)에 대해 개별적으로 MQTT 메시지를 발행합니다.

이 예제는 각 센서에 대해 개별적으로 mqtt.publish 서비스를 호출하여 5분마다 메시지를 발행합니다.

2. Python 스크립트를 사용한 반복 처리

Python 스크립트를 사용하여 반복적인 작업을 처리하고 Home Assistant의 shell_command를 통해 스크립트를 실행하는 방법도 있습니다. Python 스크립트를 작성하여 5분마다 센서 값을 MQTT로 전송합니다.

import paho.mqtt.client as mqtt
import time
import binascii

broker_address = "your_mqtt_broker_address"
broker_port = 1883
mqtt_topic_send = "home/lgac/send"
sensor_ids = ['01', '02', '03', '04']

client = mqtt.Client("LGAC_Sender")
client.connect(broker_address, broker_port)
client.loop_start()

def send_sensor_data(sensor_id):
    # 이 부분은 실제 센서 값을 가져오는 코드로 교체해야 합니다.
    operscan = "example_operscan"
    opermode = "example_opermode"
    temperature = "example_temperature"
    checksumscan = "example_checksumscan"
    ew11b_status = "example_ew11b_status"

    packet = f"8000A3{operscan}{opermode}{temperature}{checksumscan}{ew11b_status}"
    client.publish(mqtt_topic_send, packet)
    print(f"Sent data for sensor {sensor_id}: {packet}")

try:
    while True:
        for sensor_id in sensor_ids:
            send_sensor_data(sensor_id)
        time.sleep(300)  # 5분 대기
except KeyboardInterrupt:
    client.loop_stop()
    client.disconnect()

Python 스크립트를 /config/python_scripts/lgac_sender.py에 저장하고, shell_command를 통해 실행합니다. configuration.yaml에 다음을 추가합니다.

shell_command:
  start_lgac_sender: 'python3 /config/python_scripts/lgac_sender.py'

다음으로, Home Assistant가 시작될 때 Python 스크립트를 실행하도록 자동화를 설정합니다.

automation:
  - alias: "Start LGAC Sender"
    trigger:
      - platform: homeassistant
        event: start
    action:
      - service: shell_command.start_lgac_sender
  • Home Assistant 자동화: time_pattern 트리거를 사용하여 매 5분마다 각 센서의 데이터를 MQTT로 발행합니다.
  • Python 스크립트: 5분마다 반복적으로 각 센서의 데이터를 MQTT로 발행하는 스크립트를 작성하고, Home Assistant의 shell_command를 통해 실행합니다.

 

이렇게 하면 4개의 센서에 대해 5분 단위로 반복적으로 데이터를 전송하는 작업을 효과적으로 처리할 수 있습니다.

728x90

댓글