Category (Click)
개발보드 덕질하기

[LEXAN MOD] 3. 일해라 핫산! - 완성

 차완기 - @1/16/2024, 12:51:00 AM
LEXON MOD 프로젝트
21년 말 즈음 구매해 1년만에 죽어버린 무드등을 되살려 IoT 조명으로 혹사시키는 프로젝트입니다.
#HW #PCB Design #리버스 엔지니어링 #FW #ESP-IDF #HomeAssistant #MQTT
마지막 포스팅으로부터 1년 이상 지났네요.
변명이라면..만족할 수준의 펌웨어 구조를 만들지 못해ㅆ..아니 그냥 제가 게을렀습니다.
2024년을 기념해 서랍을 뒤집어 엎으면서 만들다가 만 LEXON MOD 잔해(...)를 발견하게 되어 이제서야 주섬주섬 마무리하기로 했습니다.
지난 두 포스팅에서는 PCB 분석, 역설계 등 하드웨어에 대한 내용을 다뤘다면, 이번 포스팅에서는 펌웨어와 Home Assistant와의 연동을 위한 내용을 다룹니다.

FW 목표 정하기

저의 스마트홈 시스템은 Home Assistant(이하 HA)를 사용합니다.
HA MQTT light 엔티티 지원 기능 개요 중 일부 [링크]
HA는 무수한 방식의 통신을 지원하는데요, 저는 그 중 MQTT를 통해 연결하기로 했습니다. MQTT 장비는 MQTT Discovery라는 규칙에 의해 HA Device Registry에 등록되며, 지원되는 Entity 중 light를 활용하면 원본 LEXON 무드등에서 지원하는 밝기 조절은 물론 색온도 조절 기능까지 사용할 수 있습니다.
FW는 ESPRESSIF의 공식 프레임워크인 ESP-IDF를 사용하면 되겠죠.
마지막으로 저 혼자, 그것도 순전히 취미용으로 만드는 프로젝트이기에 Wi-Fi AP 정보를 BLE로 설정하는 등 실제 상품에 들어갈법한 기능은 모두 제외하고 하드코딩했습니다.
Home Assistant
Entity: light
Function: onoff, Brightness, Color temperature
펌웨어 프레임워크: ESP-IDF
네트워크: Wi-Fi
통신 프로토콜: MQTT

Firmware Architecture

완성한 후 유지보수를 할 일이 생길지는 모르겠지만 항상 하던대로 펌웨어 구조를 짜주었습니다.
기능을 구현하는 App Layer, Wi-Fi 등 네트워크와 관련된 Callback을 처리하는 Middleware Layer, GPIO 등 하드웨어를 구동하는 Hareware Layer로 나눈 후 각 레이어의 하위 모듈을 만들었습니다.
main.c
덕분에 이런 아름다운 main이 만들어졌죠. (ESP-IDF에서는 전원 공급 후 app_main Task가 호출됨)

Hareware Layer

hw/hw.c
hw/driver/driver.c
하드웨어 레이어는 하드웨어 응용에 해당하는 LED제어와 스위치 제어를 모듈화하기 위해 두었습니다. 하위 레이어로는 Driver가 있으며, Driver 레이어 아래에 dimmer, switch 등 하드웨어 응용 모듈을 두었습니다.
본래는 Driver 레이어 위에 BSP 레이어를 두어 드라이버의 MCU측 API 의존성을 지우려고 했지만 귀차니즘으로 인해 그만뒀습니다. 조명 하나 만들겠다고 아두이노를 만들 수는 없잖아요 ㅜㅜ..

Middleware Layer

mw/mw.c
미들웨어 레이어는 시도때도없이 callback이 난무하는 네트워크를 처리하기 위해 만들었습니다. Wi-Fi를 처리하는 모듈과 mqtt를 처리하는 모듈로 나뉩니다.

Wi-Fi module

mw/wifi/wifi.h

MQTT module

mw/mqtt/mqtt.h
Wi-Fi는 다른 프로젝트에서 돌려막기 해서 친절하고 MQTT는 이번에 만들어 불친절합니다.

Application Layer

app/app.c 중 일부 - App Layer API
앞의 두 레이어에서 빌드업한 함수를 모두 활용해 기능을 구현하는 레이어입니다. app_init에서는 queue와 같은 자료구조를 init하고, app_start_task에서는 Task를 생성합니다.
app/app.c 중 일부 - Queue&Task create
App Layer 아래에는 각각의 Task가 모듈화 되어있고, Queue의 타입이 정의된 모듈? 같은게 하나 있습니다.
app/queue/mqtt_event_queue.h
Queue의 정의는 예시로 위와 같이 되어 있습니다.

network_task

app/task/network_task/network_task.c
네트워크와 관련된 부분을 모조리 처리하는 Task입니다.
Wi-Fi와 MQTT의 연결을 한 후 HA에 디바이스를 등록하기 위한 Discovery 구문을 만들어 전송하고, 사용자의 조작에 의해 넘어오는 HA측의 데이터를 분석해 App Task로 넘겨줍니다.
app/task/network_task/task_module.c 중 일부
여기서 환장할 Json 처리가 필요한데요, 자원이 빵빵한 ESP32이니 cJSON을 유감없이 마음껏 사용했습니다.
app/task/network_task/task_module.c 중 일부 - MQTT Subscribe Callback
MQTT 미들웨어에서 호출하는 MQTT Subscribe Callback이 발생하면 null-terminated string으로 바꾸어 Network Task로 날라가는 Queue에 집어넣고,
app/task/network_task/task_module.c 중 일부 - MQTT Subscribe Queue 처리
Task에서는 이를 꺼내어 구문분석을 한 후 App Task로 날라가는 Queue에 집어넣습니다.
app/task/network_task/task_module.c 중 일부 - 조명 변경 이벤트 처리
만약 App Task에서 조명의 밝기가 바뀌게 되면 이러한 정보를 MQTT로 Publish하죠.

app_task

app/task/app_task/app_task.c
조명과 스위치를 제어하는 Task입니다.
app/task/app_task/task_module.c 중 일부 - switch 제어
하루에 한 번 쓸까 말까 하더라도 수동 조작은 필수죠. 개인적으로 동작은 최대한 직관적인것을 선호하기 때문에 오직 on/off 제어만 가능하도록 해두었습니다. 절대 귀찮아서가 아니..ㅇ
app/task/app_task/task_module.c 중 일부 - switch 제어
Network Task로부터 조명 변경 데이터를 수신하면 이를 처리한 뒤 다형성(사실은 꼼수에 가까운)을 활용해 그대로 다시 던져주도록 했습니다.
여기까지 펌웨어 구조를 살펴보았는데요, 정리하면 아래와 같습니다. (실제 디렉토리 명을 사용하였습니다)
app
queue
task
app_task
network_task
mw
mqtt
wifi
hw
driver
flash
dimmer
switch

동작 확인

휴대폰 카메라의 한계로 디밍이나 색온도를 잡아내지 못하네요 ㅜㅜ.. 아무튼 잘 작동합니다.

마무리

여기까지 LEXON MOD 프로젝트를 완료하게 되었습니다.
처음에는, 특히 Fusion에서 PCB 외형 사진을 불러와 외형을 따라 그리며 이게 되려나? 싶었는데, 막상 하다 보니 생각보다 잘되어 재미나게 진행했던 프로젝트였습니다. 덕분에 3D 설계 꼼수가 하나 늘었죠 ㅋㅋ
최근에도 마음에 드는 조명을 눈여겨보고 있는데요, 조만간 비슷한 프로젝트로 또 돌아오게 될 것 같습니다  이제 슬슬 ZigBee, 특히 Matter로 넘어가는게 좋지 않을까 하는 생각이 많이 들고 있어, 다음 프로젝트에서는 ESP32-H2를 적극 활용하지 않을까 싶습니다.