YOGYUI

힐스테이트 광교산::Elfin EW11 RS485-WiFi 모듈 Migration 본문

홈네트워크(IoT)/힐스테이트 광교산

힐스테이트 광교산::Elfin EW11 RS485-WiFi 모듈 Migration

요겨 2022. 7. 3. 21:34
반응형

이전에 라즈베리파이에 RS485-USB 컨버터를 유선으로 라즈베리파이에 연결했는데, 홈네트워크 게이트웨이가 별도로 신발장 등에 존재하지 않아 거실 월패드에 직접 연결할 수 밖에 없어 미관(?)을 많이 해치고 있었다

지저분...

거실 월패드 뒤쪽 공간이 좁아 라즈베리파이랑 컨버터 등을 쑤셔넣기가 곤란해 esp32 칩을 사용한 별도의 게이트웨이 하드웨어 설계를 고려하고 있는 와중에, 집들이같이 손님을 맞이할 약속이 슬슬 생겨서 부랴부랴 일단 RS485 통신 모듈이라도 무선 방식으로 바꿔야겠다고 마음먹었다

 

어떤 걸 써볼까나... 이래저래 고민해보다가

알리 익스프레스에서 RS485 - WiFi 컨버터로 널리 사용되고 있는 Elfin-EW11A 디바이스를 2개 구매했다

(국내에 정식으로 수입되고 있진 않지만 워낙에 가성비가 좋아 홈네트워크 분야 개발자들 사이에선 개발 필수템으로 손꼽히고 있다)

개당 $16.43으로 한화로 대강 21,000원에 구매했다 (하필 또 환율이 천장을 뚫고 있을 때 샀다... 호구 ㅠ)

 

편하게 케이블을 체결하기 위해 RJ45 커넥터를 터미널블록으로 바꿔주는 액세서리도 함께 구매했다

액세서리 케이블 구매

 

액세서리 케이블의 터미널 블록 결선도는 제품 정보 페이지에서 쉽게 찾아볼 수 있다

전원석 두가닥 및 RS485 A/B 신호선으로 총 4가닥

그리고 디바이스 리셋 푸시버튼이 있어서 문제가 생겼을 때 장비를 재시작하기도 용이하다 

액세서리 결선도

디바이스 전원 입력 범위는 DC +5 ~ +36V로 굉장히 넓다!

 

1. 하드웨어 결선 및 설정

지난번 도어락 해제 연동 방법을 찾을 때, 무선 연동기의 전원이 +12V인 것을 확인했다 (링크)

 

출력 전류가 충분한지는 확신이 들진 않지만 일단 연동기 전원부에 ew11 전원을 연결했다

ew11 전원입력 - 연동기 전원입력 터미널 블록 체결

 

전류가 충분한지, 모듈 2개 모두 전원이 정상적으로 들어왔고 (녹색 LED 점등), 5분간 지켜본 결과 전원이 떨어지는 일 없이 안정적으로 유지되는 것 같아 그대로 사용하기로 했다

 

모듈 2개의 RS485 통신선은 '조명/아울렛 포트' 및 '가스밸브/전열교환기/난방/에어컨/엘리베이터 포트'에 각각 극성(A/B)를 맞춰서 체결했다

 

스마트폰으로 각 디바이스별로 구성된 WiFi AP로 접속한 뒤 웹브라우저로 10.10.100.254 주소로 접속하면 ew11 설정창으로 이동할 수 있다

※ AP SSID 이름은 ew11 케이스에 붙은 MAC 주소의 맨뒤 4자리가 포함되어 있다

예시: MAC주소가 289C-6E91-DCCA라면 SSID는 EW11_DCCA 

※ 초기 로그인 아이디: admin, 패스워드: admin

 

주요 설정 항목은 다음과 같다

(1) 'SYSTEM SETTINGS'에서 WiFi 접속 정보 설정 (관리자 계정 정보도 바꾸는것을 추천)

  • WiFi Mode를 AP+STA로 설정 (ew11의 AP 모드를 해제하려면 STA로 선택)
  • 'Scan' 버튼을 클릭해 자신의 WiFi SSID 선택
  • WiFi 비밀번호 입력

설정이 완료되면 화면 맨 아래로 스크롤해서 'Submit' 버튼 클릭

 

(2) 'Serial Port Settings'에서 RS485 관련 파라미터 설정

  • Baud Rate: 9600
  • Data Bit: 8
  • Stop Bit: 1
  • Parity: None
  • Gap Time: 50
  • Flow Control: Disable (화면 캡쳐를 잘못해서 Half Duplex로 나왔다..)

RS485 시리얼 통신 파라미터 설정

마찬가지로 맨 아래로 스크롤하여 'Submit' 버튼 클릭

 

(3) 'Communication Settings'에서 네트워크 설정

RS485 Raw 패킷을 주고받는 통신 인터페이스는 여러가지 방식이 있는데, ew11을 TCP 혹은 UDP 서버로 설정하는게 코드를 구성하는게 가장 간단하다 (TCP 서버 설정 시 accept 수 제한을 설정할 수 있어, 다른 클라이언트가 접속하려는 것을 막을 수도 있다)

설정 가능한 통신 인터페이스
통신 인터페이스 설정

TCP 혹은 UDP 서버로 인터페이스를 설정했다면, 접속 포트를 변경하거나 혹은 default값이 8899를 외워두면 된다 

마찬가지로 맨 아래로 스크롤하여 'Submit' 버튼 클릭

 

(4) 디바이스 리셋

'OTHERS' 탭에서 'Restart' 버튼을 클릭해서 디바이스를 리셋해주자

 

(5) WiFi 접속 여부 확인

시스템 설정이 정상적으로 이루어졌다면, 자신의 WiFi에 ew11이 정상적으로 접속하였고, IP 주소가 DHCP로 정상적으로 할당된 것을 확인할 수 있다 ('STATUS' 탭)

WiFi 접속 상태 확인

ew11 각 디바이스별로 할당된 IP 주소와 TCP 혹은 UDP 포트 두 개 정보로 TCP 클라이언트가 접속해야하므로 값을 잘 기억해두자

(혹은 공유기의 포트포워딩 기능을 활용해서 DDNS로 접속해도 좋지만, 어차피 집안 내부 네트워크 내에 접속된 장비들끼리 통신할꺼라 DHCP로 할당된 내부 IP주소만 알고있어도 충분하다)

 

(6) 시리얼 통신 간이 확인

시리얼 통신 파라미터가 정상적으로 설정되었고, 하드웨어 결선을 정확히 했다면 다음과 같이 주황색 LED가 점멸하는 것을 눈으로 확인할 수 있다

 

(7) 하드웨어 정리

ew11은 폭 61mm, 너비 26mm, 높이 18mm로 굉장한 소형 디바이스이기 때문에, 2개 정도는 거실 월패드 뒷면 공간에 충분히 쑤셔넣을 수 있었다 (액세서리 케이블을 한바퀴 돌려서 케이블타이로 묶어줘야했던게 약간 번거로운 작업)

 

다 쑤셔넣고 나면 거실 월패드를 다시 원상복구해주면 된다

※ GPIO를 통한 도어락 연동은 일단 지금 단계에서는 포기! 나중에 소형 보드를 만들때 GPIO 제어 넣는걸 깜빡하지 않도록 메모..

 

월패드 밖으로 튀어나온 선을 없애고나니 속이 다 시원하다 ㅎㅎ

 

2. 소프트웨어 변경

기존에 USB-RS485 컨버터에 종속적으로 동작하도록 코드를 짰기 때문에 여러모로 수정사항이 많았다 ㅠ

일단 구성 환경에 따라 USB-RS485 혹은 WiFi-RS485 모듈 둘 중 하나를 선택할 수 있도록 하기 위해 RS485 wrapper 클래스를 하나 만들어줬다

 

[RS485Comm.py]

import os
import sys
from enum import IntEnum
from typing import Union
from Serial import *
from Socket import *
CURPATH = os.path.dirname(os.path.abspath(__file__))  # {$PROJECT}/Include/RS485
INCPATH = os.path.dirname(CURPATH)  # {$PROJECT}/Include/
sys.path.extend([CURPATH, INCPATH])
sys.path = list(set(sys.path))
del CURPATH, INCPATH
from Common import writeLog, Callback

class RS485HwType(IntEnum):
    Serial = 0
    Socket = 1
    Unknown = 2

class RS485Config:
    enable: bool = True
    comm_type: RS485HwType
    serial_port: str = '/dev/ttyUSB0'
    serial_baud: int = 9600
    socket_ipaddr: str = '127.0.0.1'
    socket_port: int = 80

class RS485Comm:
    _comm_obj: Union[SerialComm, TCPClient, None] = None
    _hw_type: RS485HwType = RS485HwType.Unknown
    _last_conn_addr: str = ''
    _last_conn_port: int = 0
    
    def __init__(self, name: str = 'RS485Comm'):
        self._name = name
        self.sig_connected = Callback()
        self.sig_disconnected = Callback()
        self.sig_send_data = Callback(bytes)
        self.sig_recv_data = Callback(bytes)
        self.sig_exception = Callback(str)

    def setType(self, comm_type: RS485HwType):
        if self._comm_obj is not None:
            self.release()
        if comm_type == RS485HwType.Serial:
            self._comm_obj = SerialComm(self._name)
        elif comm_type == RS485HwType.Socket:
            self._comm_obj = TCPClient(self._name)
        self._hw_type = comm_type
        self._comm_obj.sig_connected.connect(self.onConnect)
        self._comm_obj.sig_disconnected.connect(self.onDisconnect)
        self._comm_obj.sig_send_data.connect(self.onSendData)
        self._comm_obj.sig_recv_data.connect(self.onRecvData)
        self._comm_obj.sig_exception.connect(self.onException)
        writeLog(f"Set HW Type as '{comm_type.name}'", self)

    def getType(self) -> RS485HwType:
        return self._hw_type

    def release(self):
        if self._comm_obj is not None:
            self._comm_obj.release()
            del self._comm_obj
        self._comm_obj = None

    def connect(self, addr: str, port: int) -> bool:
        # serial - (devport, baud)
        # socket - (ipaddr, port)
        self._last_conn_addr = addr
        self._last_conn_port = port
        return self._comm_obj.connect(addr, port)

    def disconnect(self):
        self._comm_obj.disconnect()

    def reconnect(self, count: int = 1):
        self.disconnect()
        for _ in range(count):
            if self.isConnected():
                break
            self.connect(self._last_conn_addr, self._last_conn_port)

    def isConnected(self) -> bool:
        if self._comm_obj is None:
            return False
        return self._comm_obj.isConnected()
    
    def sendData(self, data: Union[bytes, bytearray, str]):
        self._comm_obj.sendData(data)

    def time_after_last_recv(self) -> float:
        return self._comm_obj.time_after_last_recv()

    # Callbacks
    def onConnect(self, success: bool):
        if success:
            self.sig_connected.emit()
        else:
            self.sig_disconnected.emit()

    def onDisconnect(self):
        self.sig_disconnected.emit()

    def onSendData(self, data: bytes):
        self.sig_send_data.emit(data)

    def onRecvData(self, data: bytes):
        self.sig_recv_data.emit(data)

    def onException(self, msg: str):
        self.sig_exception.emit(msg)

    @property
    def name(self) -> str:
        return self._name

RS485Comm 객체는 내부 변수로 유선(UART/USART) 시리얼 통신을 담당하는 SerialComm 혹은 소켓 통신을 담당하는 TCPClient 객체를 갖고 있으며, 사용자 설정에 따라 취사 선택을 할 수 있게 구성했다

(SerialComm, TCPClient 클래스 모두 기존에 직접 개발했던 코드들을 일부 수정해서 가져왔다)

 

실제로 사용할 때는 스크립트 실행 전 config.xml 설정파일 내부 텍스트를 바꿔주면 된다

(내가 헷갈리기 싫어서 XML 태그 이름들도 상당히 직관적으로 구현했다 ㅋㅋ)

<config>
    <rs485>
        <energy>
            <enable>1</enable>
            <type>1</type>  <!-- 0 = usb2serial, 1 = ew11 -->
            <usb2serial>
                <port>/dev/rs485_energy</port>
                <baud>9600</baud>
            </usb2serial>
            <ew11>
                <ipaddr>192.168.0.1</ipaddr>
                <port>8899</port>
            </ew11>
        </energy>
    </rs485>

 

RS485 통신과 관련해서 약식으로 UML을 그려보면 다음과 같다

(본인 스스로 코드 구조 간결화를 위해 작성한것인지라 UML 기본양식은 크게 신경쓰지 않았다)

힐스테이트 광교산 RS485 통신 관련 소스코드 UML

별 기능도 안하는 코드 주제에 살짝 복잡한 느낌은 있지만, 최대한 객체지향 느낌을 살려서 구현해나간 덕분에 디버깅 시간이 크게 단축된 것을 개인적으로 체감했다 (모듈간 독립성은 크게 신경쓰지는 않았다)

코드 유지보수 측면에서도 꽤 강점을 보일 것으로 기대하고 있다

 

전체 코드는 깃허브에 socket-ew11 브랜치로 커밋했다

https://github.com/YOGYUI/HomeNetwork/tree/socket-ew11

 

GitHub - YOGYUI/HomeNetwork: HomeNetwork(Homebridge) Repo

HomeNetwork(Homebridge) Repo. Contribute to YOGYUI/HomeNetwork development by creating an account on GitHub.

github.com

(아이파크 거주자분의 요청이 있어 광교아이파크 코드에도 동일하게 ew11을 적용할 수 있게 코드를 수정)

 

위에서 언급한 것 외에도, 유선 방식을 무선 방식으로 migration하면서 발생하는 타이밍 이슈등 하고 싶은 이야기는 많지만, 글로 적자니 왠지 귀찮아서 그냥 소스코드에 주석으로만 남겨뒀다


이제 깔끔해진 거실 월패드가 애플 홈킷 및 구글 어시스턴트랑 제어 연동이 된다

 

위에서도 언급했듯이, 도어락 해제는 GPIO 제어가 필요하기 때문에 이대로 만족하면서 끝낼 생각은 없다

조그마한 PCB 보드 하나 설계해서 전체 기능을 integration해볼 계획

(어차피 esp32같은 wifi 지원되는 MCU랑 RS485-USART 변환칩 2개면 지금까지 구현한 기능은 모두 통합할 수 있다... 다만 아직 현관 비디오폰을 연동해보지 못했기에 여유를 가지고 천천히 해 볼 생각)

 

반응형
Comments