아두이노에서의 이벤트 기반 프로그래밍: 임베디드용 엔터프라이즈 패턴
아두이노는 전통적으로 선형 스케치와 순환하는 loop()와 연관되어 있습니다. 하지만 프로젝트가 점점 복잡해지면 이 접근 방식은 스파게티 코드로 이어지게 됩니다. 우리는 EVA Core 라이브러리를 활용해 엔터프라이즈 애플리케이션의 이벤트 기반 아키텍처를 임베디드 개발로 가져오는 방법을 소개합니다.
마이크로컨트롤러의 구조 문제
데스크톱과 서버 애플리케이션에서는 코드가 이벤트 핸들러 중심으로 구성됩니다: 클릭, 타이머, 네트워크 패킷 등입니다. 반면 아두이노는 모든 것을 단일 loop()에 밀어 넣어야 하며, millis()와 플래그를 사용해 상태를 수동으로 추적해야 합니다. 이로 인해 두 가지 치명적인 제한이 생깁니다:
- 컴포넌트 로직을 격리할 수 없음 — 타이밍이 전체 객체 계층 구조를 통해 전파됨
- 모듈 간 타이밍 요구사항에 대한 명확한 계약 부재
표준 아두이노 라이브러리는 이를 부분적으로만 해결합니다: 버튼 핸들러나 별도의 타이머는 있지만 통합된 이벤트 시스템은 없습니다. "올인원" 솔루션을 만들려는 시도는 특정 loop() 구현에 대한 강한 결합으로 이어집니다.
EVA Core: 통합 시스템의 세 가지 구성 요소
EVA Core 라이브러리(Event-Driven Architecture)는 세 가지 상호작용 레이어를 통해 이 문제를 해결합니다:
- 클래스 메서드용 콜백 메커니즘 — 전역 함수 없이 이벤트에 구독할 수 있음
- Tickable 인터페이스 — tick() 메서드를 통해 객체가 시간 루프에 접근
- Survival Kit — 앞선 두 원칙에 기반한 즉시 사용 가능한 컴포넌트 세트(버튼, 타이머)
핵심 혁신은 타이밍 로직을 비즈니스 규칙에서 분리하는 것입니다. 센서는 자체 타이밍을 관리하고, 애플리케이션은 이벤트에만 반응합니다. 이를 통해 다음이 가능해집니다:
- 컴포넌트 내 필터링과 디바운싱 알고리즘 격리
- 중첩 객체의 업데이트 리듬 보장
- 계층 구조를 통한 호출 전파 필요성 제거
온도 센서 예제를 통해 구현 방식을 살펴보겠습니다:
#include <evaTickable.h>
#include <evaHandler.h>
using namespace eva;
class TempSensor : public Tickable {
private:
IHandler* listener = nullptr;
unsigned long lastRead = 0;
public:
void subscribe(IHandler* handler) {
listener = handler;
}
private:
void tick() override {
if (millis() - lastRead > 1000) { // Interval 1 sek
int value = readTemperature();
if (listener && value != lastValue) {
CallbackInfo info;
info.eventType = TEMP_UPDATE;
info.eventArg = value;
listener->invoke(this, info);
}
lastRead = millis();
}
}
};
class ClimateControl : public IHandler {
private:
TempSensor sensor;
void onTempUpdate(int value) {
if (value > 25) activateCooling();
}
public:
ClimateControl() {
sensor.subscribe(this);
}
void invoke(void* sender, CallbackInfo info) override {
if (info.eventType == TEMP_UPDATE) {
onTempUpdate(info.eventArg);
}
}
};
void setup() {
static ClimateControl system;
}
void loop() {
eva::tac(); // Edinaya point update
}
이 접근 방식의 장점:
- TempSensor가 폴링과 필터링 로직을 캡슐화
- ClimateControl은 이벤트만 알고 타이밍 세부 사항은 모름
- 새로운 센서 추가 시 loop() 변경 불필요
- 타이밍과 분리된 비즈니스 로직 단독 테스트 가능
주변 장치용 레고 아키텍처
Survival Kit은 컴파일 타임 템플릿을 통해 구성 원리를 구현합니다. 예를 들어 아날로그 입력에서의 멀티 버튼 핸들링은 트랜스포머 체인으로 구축됩니다:
template <int PIN, int PIN_MODE, signed short... LEVELS>
using PinMultiButton = Button<QuantizeDecor<DebounceDecor<AnalogPinReader<PIN, PIN_MODE>>, LEVELS...>>;
신호 처리 아키텍처:
- 아날로그 신호 →
- 디바운싱 →
- 레벨별 양자화 →
- 버튼 로직 →
- 이벤트
이 패턴은 즉시 사용 가능한 컴포넌트로부터 커스텀 핸들러를 조립할 수 있게 합니다. 예 — 개별 버튼으로 구성된 키패드:
#include <evaSwitch.h>
class MyKeypad {
public:
MyKeypad() {
pinMode(2, INPUT_PULLUP);
// ... initialization ostalnykh pinov
}
signed short getValue() {
if (digitalRead(2) == LOW) return 'u';
// ... processing drugikh knopok
return 0;
}
};
// Withborka chains processing
Switch<DebounceDecor<MyKeypad>> keypad;
// Podpiska on events
keypad.setListener(new Handler<App>(this, &App::onKeyPress), ON_PRESS);
각 레이어는 자신의 책임을 처리합니다. 필요하다면 디바운싱을 더 정교한 알고리즘으로 교체하거나 프레스 지속 시간 필터링을 추가할 수 있습니다 — 코어 애플리케이션 로직을 변경하지 않고요.
제한사항과 사용 사례
EVA Core는 범용 솔루션이 아니라 방법론 도구로 자리매김합니다. 다음 경우에 사용하세요:
- 복잡한 객체 계층 구조를 가진 프로젝트 구축 시
- 타이밍과 비즈니스 로직의 명확한 분리가 필요할 때
- 간단한 장치에서 이벤트 기반 아키텍처 원리를 가르칠 때
주요 제한사항:
- 가상 테이블로 인한 RAM 사용량 증가
- 초보자에게 복잡함(템플릿과 메서드 포인터 지식 필요)
- 간단한 프로젝트에 과도(예: 단일 LED 제어)
중요한 점: 이 라이브러리의 목표는 아키텍처 패턴 시연이지 자원 최적화가 아닙니다. 제약이 엄격한 생산 프로젝트에서는 오버헤드를 분석해야 합니다.
주요 요점
- 이벤트 모델은 컴포넌트에서 타이밍 의존성을 격리
- Tickable 인터페이스는 중첩 객체의 규칙적 업데이트 보장
- 템플릿 구성은 런타임 오버헤드 없이 유연성 제공
- 호출 전파 불필요로 리팩토링과 테스트 간소화
- 아두이노가 엔터프라이즈 패턴 실험장이 됨
이 접근 방식은 자원이 제한된 마이크로컨트롤러에서도 전문 설계 방법을 적용할 수 있음을 증명합니다. 핵심 이점은 코드가 예측 가능하고 확장성 있게 변한다는 점으로, 단순히 "일단 작동하는" 수준을 넘어섭니다.
— Editorial Team
아직 댓글이 없습니다.