블로그 목록
AI / IoT

AI로 IoT 펌웨어 개발하기: MCP 서버와 자동화 테스트

2024년 12월 1일
15분
AI로 IoT 펌웨어 개발하기: MCP 서버와 자동화 테스트

개요

임베디드 시스템 개발은 전통적으로 하드웨어와 소프트웨어의 긴밀한 상호작용이 필요한 영역입니다. 이 글에서는 Jest가 MCP 서버를 통해 실제 nRF52840 하드웨어와 통신하고, AI(Claude Code)가 테스트 결과를 확인하며 펌웨어를 개발하는 방법을 소개합니다.

프로젝트 구성

이 시스템은 두 개의 핵심 프로젝트로 구성됩니다:

1. iot-ai (펌웨어)

  • 플랫폼: nRF52840 (Nordic Semiconductor)
  • RTOS: Zephyr RTOS
  • 언어: C++
  • 주요 기능: BLE 통신, 센서 데이터 수집, 다중 디바이스 연결

2. nrf-mcp-server (MCP 서버)

  • 언어: TypeScript / Node.js
  • 역할: Jest 테스트와 하드웨어 간의 브릿지
  • 기능: 시리얼 통신, 펌웨어 플래싱, 로그 스트리밍

아키텍처

Jest 테스트

MCP 서버를 통해 명령 전송

nRF52840 펌웨어 (C++)

로그 출력

Jest ← 로그 패턴 매칭

테스트 결과 & 로그

Claude Code

테스트 결과와 로그를 분석하여 코드 수정

MCP 서버의 핵심 도구들

MCP 서버는 Jest 테스트가 하드웨어를 제어할 수 있도록 7가지 도구를 제공합니다:

도구설명
list_devices연결된 nRF 디바이스 목록 조회
connect_serial시리얼 포트 연결 및 로그 수집 시작
disconnect_serial시리얼 연결 해제
flash_firmware펌웨어 빌드 및 플래싱
write_serial디바이스에 명령 전송
get_serial_status연결 상태 확인
reset_devices하드웨어 리셋

테스트 주도 개발 (TDD) 워크플로우

이 시스템의 핵심은 로그 기반 테스트입니다.

1단계: 테스트 의도 정의 (사람)

# 재시도 테스트 의도
- Central → Peripheral 메시지 전송 중 실패 시 자동 재시도
- 최대 3회 재시도 후 실패 콜백 호출
- 재시도 간격은 100ms

2단계: 테스트 코드 작성 (AI)

AI가 테스트 의도를 기반으로 Jest 테스트 코드를 작성합니다:

test("comm 레이어 재시도 테스트", async () => {
  await pairDevices("기기1", "기기2", passkey);

  // 패킷 거부 설정
  await writeSerial("기기1", "peripheral reject");
  await waitForNewLog("기기1", /Packet acceptance set to: false/);

  // comm을 통한 데이터 전송
  await writeSerial("기기2", "comm send 1 TestData");
  await waitForNewLog("기기2", /Comm: Data enqueued/);

  // 패킷 거부 및 재시도 확인
  await waitForNewLog("기기1", /Comm: Write rejected/);
  await waitForNewLog("기기2", /Scheduling retry/);

  // 패킷 승낙으로 전환
  await writeSerial("기기1", "peripheral accept");
  await waitForNewLog("기기1", /Packet acceptance set to: true/);

  // 재시도 후 전송 성공 확인
  await waitForNewLog("기기2", /Send success to device ID 1/);
  await waitForNewLog("기기1", /Data received from device ID 1/);
});

3단계: 펌웨어 구현 (AI)

Claude Code가 테스트 요구사항을 읽고 C++ 코드를 생성합니다:

void Comm::send_with_retry(uint8_t device_id, const char* data) {
    for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
        LOG_INF("Retry attempt %d", attempt + 1);

        if (send_internal(device_id, data)) {
            LOG_INF("Send success to device ID %d", device_id);
            return;
        }

        k_msleep(RETRY_INTERVAL_MS);
    }

    LOG_ERR("Send failed after %d attempts", MAX_RETRIES);
    notify_failure(device_id);
}

4단계: 빌드 및 테스트 실행

npm run test:shell
# 1. CMake 빌드
# 2. nrfjprog으로 플래싱
# 3. Jest 테스트 실행
# 4. 로그 패턴 검증

펌웨어 레이어 아키텍처

펌웨어는 명확한 계층 구조로 설계되어 AI가 각 레이어를 독립적으로 수정할 수 있습니다:

Application (센서, 비즈니스 로직)
        ↓
Comm Layer (큐잉, 재시도, 브로드캐스트)
        ↓
Connectivity Layer (device_id 추상화)
        ↓
Peripheral/Central (GATT 서버/클라이언트)
        ↓
BtAuth (블루투스 페어링 & 보안)
        ↓
Zephyr BLE API

주요 설계 결정

1. 로그 기반 테스트를 선택한 이유

  • 실제 하드웨어에서 실행되어 타이밍 이슈 발견 가능
  • 모킹 없이 실제 BLE 통신 검증
  • 로그 패턴 매칭으로 상태 변화 추적

2. Device ID 추상화

  • bt_conn 포인터 대신 숫자 ID(1-N) 사용
  • 재연결 시에도 일관된 디바이스 식별
  • MAC 주소 기반으로 NVS에 영구 저장

3. 지수 백오프 재시도

  • 5단계 재시도: 100ms → 250ms → 500ms → 1s → 4s
  • 실패 시 이벤트 리스너에 알림
  • 브로드캐스트 시 특정 디바이스 제외 가능

실제 구현 예시

BLE 연결 관리

// connectivity.cpp
void Connectivity::on_connected(bt_conn* conn, uint8_t role) {
    // MAC 주소로 기존 device_id 찾기
    bt_addr_le_t addr;
    bt_conn_get_info(conn, &info);

    int device_id = find_by_address(&addr);
    if (device_id < 0) {
        device_id = allocate_new_id();
        save_to_nvs(device_id, &addr);
    }

    LOG_INF("Connection completed - device_id=%d", device_id);
}

메시지 큐 시스템

// comm.cpp
class SendQueue {
    struct QueueItem {
        uint8_t device_id;
        uint8_t data[MAX_DATA_LEN];
        uint8_t retry_count;
        int64_t next_retry_time;
    };

    std::array<QueueItem, QUEUE_SIZE> items;

    void process() {
        for (auto& item : items) {
            if (k_uptime_get() >= item.next_retry_time) {
                if (!try_send(item)) {
                    schedule_retry(item);
                }
            }
        }
    }
};

핵심 성과

지표
펌웨어 코드6,277줄 (C++)
MCP 서버 코드3,215줄 (TypeScript)
테스트 파일39개 Jest 테스트
최대 동시 연결6개 (5 peripheral + 1 central)
보안 레벨BLE Level 4 (Authenticated SC)

결론

이 프로젝트는 Jest 테스트가 하드웨어와 통신하고, AI가 그 결과를 확인하며 임베디드 시스템을 개발할 수 있음을 보여줍니다.

핵심 인사이트:

  1. 레이어 분리가 AI 개발을 가능하게 함: 명확한 계층 구조 덕분에 Claude가 한 레이어를 수정해도 다른 부분이 깨지지 않음
  2. 로그 기반 테스트가 효과적: 복잡한 모킹 없이도 실제 하드웨어 동작을 검증 가능
  3. MCP가 하드웨어 접근을 추상화: Jest 테스트가 MCP 서버를 통해 시리얼 포트, 플래싱 등 저수준 작업을 수행
  4. 사람은 "무엇을", AI는 "어떻게": 테스트 의도와 아키텍처는 사람이, 구현 세부사항은 AI가 담당

이 접근법은 임베디드 시스템 개발의 새로운 가능성을 열어줍니다.