AEGLOSLAB Arduino UNO Q 완전 가이드

제12부 실전 프로젝트

12.1 종합 프로젝트 ① IoT 대시보드

센서 데이터를 MCU가 읽고, Bridge로 MPU에 넘기고, DB에 쌓고, Wi-Fi를 거쳐 브라우저에서 실시간으로 본다 — UNO Q의 "듀얼 브레인"이 왜 하나의 보드에 있는지를 가장 직관적으로 보여주는 조합이다.

이 장에서는 온습도 센서 값을 STM32U585(MCU)에서 주기적으로 읽어, Arduino_RouterBridge를 통해 QRB2210(MPU) 위의 Python 앱으로 전달하고, SQLite 또는 PostgreSQL에 적재한 뒤, Wi-Fi로 접속 가능한 웹 대시보드로 시각화하는 전체 파이프라인을 끝까지 만든다. 분기점마다(스토리지 종류, 대시보드 구현 방식) 트레이드오프를 펼쳐 두므로, 프로젝트 규모에 맞게 골라 조합하면 된다.

⚠️ 정직성 표기: 이 장의 Bridge·Brick API 문법은 이 책 3.4·4.2에서 이미 공식 자료로 확인한 패턴을 그대로 따르고, DB 스키마는 7.3·7.4에서 확인한 예제를 재사용한다. 다만 이 장의 코드를 하나로 합친 전체 App은 저자가 실물 보드에서 끝까지 실행 검증하지 못했다 — 특히 Modulino 라이브러리의 정확한 메서드 시그니처와 WebUI 브릭의 세부 반환 형식은 App Lab 버전에 따라 달라질 수 있다. 배포 전 App Lab 콘솔 로그로 반드시 재확인한다(4.2.5의 정직성 노트와 같은 태도).

12.1.1 이 프로젝트가 전제하는 장

이 프로젝트는 여러 부(部)의 내용을 조합한다. 아래 장들을 먼저 읽었다는 전제로 진행한다(목차 4).

  • 2.1 두 프로세서 아키텍처 — Wi-Fi/BT 콤보 칩(WCBN3536A)이 MPU 쪽에 연결되어 있다는 사실의 근거
  • 2.2 핀아웃과 GPIO·PWM·아날로그 — Qwiic 커넥터(I2C4)
  • 2.3 고속 인터페이스 — Qwiic 커넥터(QWIIC1)의 전기적 사양
  • 3.1 Debian Linux — MPU 측 OS, Python 실행 환경
  • 3.2 Zephyr 기반 Arduino Core — MCU 측 스케치 실행 환경
  • 3.3 Arduino App Lab 아키텍처 — App·Brick 개념, "Real-Time Dashboard"라는 이름의 실체
  • 3.4 Bridge — Bridge.notify()·Bridge.provide()의 근거, 데드락 함정
  • 4.2 App Lab으로 MPU-MCU 연동 앱 만들기 — app.yaml·프로젝트 구조·WebUI 브릭 예고
  • 4.3 GPIO·센서 예제 모음 — 표준 Wire(A4/A5) I2C 이슈와 Qwiic(Wire1) 우회
  • 4.4 Wi-Fi·Bluetooth 통신 — 대시보드 접속 경로(집필 순서상 이 장 뒤에 올 수 있다 — 목차 4 참고)
  • 10.3 Qwiic 생태계와 커뮤니티 확장보드 — Modulino 제품 라인(10.3.4)의 실체, 가격 "확인 필요" 표기의 근거(10.3.9)
  • 7.3 / 7.4 PostgreSQL / SQLite 설치와 활용 — 저장 계층 선택지(이 장이 실제로 사용하는 스키마의 출처)

12.1.2 무엇을 만드나 — 전체 파이프라인

Bridge.notify() (3.4) Wi-Fi (2.1, 4.4) Modulino 센서 (Qwiic·I2C4, 2.3) MCU 스케치 STM32U585/Zephyr (3.2) Python 앱 MPU/Debian (3.1, 4.2) SQLite 또는 PostgreSQL (7.4 / 7.3) 웹 대시보드 WebUI 브릭 또는 커스텀 웹앱 브라우저 PC·스마트폰

읽는 순서: ① Modulino 센서 값을 MCU가 Qwiic(I2C4)로 읽는다. ② MCU가 Bridge.notify()로 MPU에 알린다 — 응답을 기다리지 않는 편도 전송이라 센서 주기 전송에 적합하다(3.4.4). ③ Python 앱이 값을 DB에 적재한다. ④ 웹 대시보드가 DB(또는 실시간 스트림)를 읽어 브라우저에 표시한다. ⑤ 같은 Wi-Fi망의 어떤 기기에서든 브라우저로 접속한다.

용어 정정: App Lab 제품 소개가 내세우는 "Real-Time Dashboard"(3.3.2)는 이 장에서 만드는 커스텀 센서 대시보드와 다른 것이다. 3.3.5에서 확인했듯 그 이름은 App Lab Settings 화면(CPU·디스크·네트워크 등 시스템 상태)에 대응하는 것으로 보이며, "센서 값을 그래프로 보여주는" 이 장의 대시보드는 App Lab의 WebUI 브릭(4.2.8)을 직접 조립해 만드는 것이다. 이름이 비슷해 혼동하기 쉬우니 구분해 둔다.

12.1.3 프로젝트 구조와 app.yaml

App Lab 프로젝트 구조는 4.2.1의 규약을 그대로 따른다.

iot_dashboard/
├── app.yaml
├── python/
│   ├── main.py
│   └── requirements.txt
└── sketch/
    ├── sketch.ino
    └── sketch.yaml
# app.yaml
name: IoT Sensor Dashboard
description: "MCU가 Modulino 센서를 읽어 Bridge로 넘기고, MPU가 DB에 적재·웹으로 시각화하는 예제"
icon: 📊
version: "0.1.0"

# WebUI 브릭 경로(12.1.6 ①)를 쓸 때만 필요 — 실제 노출 포트는 App Lab 콘솔에서 확인(확인 필요)
ports: []
bricks: []   # WebUI 브릭을 쓰면 여기 등록이 필요할 수 있다 — 4.2.8·9.2.5의 bricks 선언 패턴 참고

12.1.4 1단계 — MCU에서 센서 읽고 Bridge로 보내기

Modulino 센서군은 Qwiic 커넥터(QWIIC1)로 배선 없이 I2C4(Wire1)에 연결된다(2.2.5, 2.3.5). 4.3.6이 보고한 실제 이슈 — 표준 Wire(A4/A5, I2C3)가 I2C 장치를 인식하지 못하는 문제(GitHub Issue #301) — 는 Qwiic(Wire1, I2C4)로 옮기면 우회된다고 보고돼 있으므로, Modulino를 Qwiic으로 쓰는 이 프로젝트는 애초에 그 함정을 피해 간다.

sketch/sketch.ino:

#include <Arduino_RouterBridge.h>
#include <Modulino.h>

ModulinoThermo thermo;

unsigned long lastSampleMs = 0;
const unsigned long SAMPLE_INTERVAL_MS = 2000;  // 2초마다 샘플링

void setup() {
  Bridge.begin();
  Monitor.begin(115200);

  Modulino.begin();   // Qwiic(Wire1, I2C4) 버스 초기화 — 검증 필요(라이브러리 버전별 시그니처)
  thermo.begin();
}

void loop() {
  unsigned long now = millis();
  if (now - lastSampleMs >= SAMPLE_INTERVAL_MS) {
    lastSampleMs = now;

    float tempC    = thermo.getTemperature();  // 공식 예제(Local Weather Station)에서 확인된 메서드명
    float humidity = thermo.getHumidity();      // 위와 동일

    // sensor_name/value 쌍으로 보낸다 — 7.3·7.4의 롱 포맷 스키마와 그대로 호환된다
    Bridge.notify("push_sample", "temp_c", tempC);
    Bridge.notify("push_sample", "humidity_pct", humidity);
  }
}

다른 Modulino(Distance, Movement 등)를 추가하려면 같은 패턴으로 push_sample("distance_mm", d)처럼 이름만 늘리면 된다 — 다만 각 모듈의 정확한 getter 메서드명은 이 책에서 확인하지 못했으므로 실제 라이브러리 헤더나 Arduino 공식 Modulino 문서로 재확인한다.

Bridge.notify()는 Bridge.call()과 달리 응답을 기다리지 않는 편도 전송이다 — 센서 주기 전송처럼 "보내고 잊어버려도" 되는 데이터에 적합하다(3.4.4). provide()/provide_safe() 콜백 안에서 Bridge.call()·Monitor.print()·Serial.print()를 부르면 안 된다는 데드락 함정(3.4.4)은 이 스케치처럼 loop()에서 능동적으로 notify()만 보내는 구조에서는 해당하지 않는다 — 그 함정은 아래 12.1.6에서 Python이 MCU 함수를 부르는 반대 방향 구조를 설계할 때 다시 등장한다.

엣지케이스: I2C 버스에 여러 Modulino를 데이지체인으로 연결하면 각 모듈이 고정 주소를 쓰므로 동일 종류(Thermo 2개 등)를 중복 연결할 수 없다(주소 충돌). 온도 노드를 여러 개 두고 싶다면 각기 다른 UNO Q로 분리하거나 I2C 멀티플렉서를 검토한다 — 이 경우 12.1.9의 다중 노드 확장과 만난다.

12.1.5 2단계 — Python 앱에서 데이터 수신과 저장

python/main.py에서 arduino.app_utils의 Bridge·App으로 MCU가 보낸 알림을 받는다. 정확한 임포트·진입점 문법은 3.4.5·4.2.3에서 확인한 공식 패턴(from arduino.app_utils import *, App.run(user_loop=loop))을 그대로 따른다.

SQLite 경로 (단일 보드, 로컬 저장, 설정 없이 즉시 사용 — 스키마는 7.4.3 그대로 재사용):

# python/main.py
from arduino.app_utils import *
import sqlite3
import time

DB_PATH = "/home/arduino/data/sensors.db"

def init_db():
    conn = sqlite3.connect(DB_PATH)
    conn.execute("PRAGMA journal_mode = WAL;")   # 로거(쓰기)와 대시보드(읽기) 동시 접근 — 7.4.4
    conn.execute("""
        CREATE TABLE IF NOT EXISTS sensor_log (
            id          INTEGER PRIMARY KEY AUTOINCREMENT,
            sensor_name TEXT NOT NULL,
            value       REAL NOT NULL,
            ts          TEXT NOT NULL DEFAULT (datetime('now'))
        )
    """)
    conn.execute(
        "CREATE INDEX IF NOT EXISTS idx_sensor_log_name_ts ON sensor_log (sensor_name, ts DESC);"
    )
    conn.commit()
    conn.close()

def push_sample(sensor_name, value):
    conn = sqlite3.connect(DB_PATH)
    conn.execute(
        "INSERT INTO sensor_log (sensor_name, value) VALUES (?, ?)", (sensor_name, value)
    )
    conn.commit()
    conn.close()
    return True

init_db()
Bridge.provide("push_sample", push_sample)

def loop():
    time.sleep(1)   # 이 App은 MCU가 능동적으로 notify하므로, 메인 루프는 유휴 대기만 한다

App.run(user_loop=loop)

PostgreSQL 경로 (여러 UNO Q를 중앙 서버로 모으거나, 동시 쓰기·네트워크 접속이 필요할 때 — 스키마는 7.3.4 그대로 재사용):

from arduino.app_utils import *
import psycopg
import time

conn = psycopg.connect("host=192.168.0.50 dbname=sensordb user=sensoruser password=...")
conn.autocommit = True

def push_sample(sensor_name, value):
    with conn.cursor() as cur:
        cur.execute(
            "INSERT INTO sensor_readings (sensor_name, value) VALUES (%s, %s)",
            (sensor_name, value),
        )
    return True

Bridge.provide("push_sample", push_sample)

def loop():
    time.sleep(1)

App.run(user_loop=loop)

psycopg(또는 psycopg2)는 sudo apt install python3-psycopg2 또는 pip install "psycopg[binary]"로 설치한다(7.3.4).

선택 기준(강요 없음 — 규모로 고른다, 7.4.2 표 요약):

기준① SQLite (7.4)② PostgreSQL (7.3)
설정 부담없음 — 파일 하나서버 설치·계정·네트워크 설정 필요
적합 규모보드 1대, 로컬 대시보드만보드 여러 대를 한 서버로 집계
동시 쓰기WAL 모드로 "쓰기 1 + 읽기 다수"는 무난다중 클라이언트 동시 접속을 기본 전제
원격 접근파일 자체엔 네트워크 접근 불가(앱을 거쳐야 함)TCP로 원격 접속 기본 지원
eMMC 쓰기 마모WAL·배치 커밋으로 완화 가능(7.4.4)서버가 별도 장치면 UNO Q eMMC 부담 없음

엣지케이스: SQLite는 기본(rollback journal) 모드에서 쓰기 도중 파일 전체를 잠근다 — 웹 대시보드가 읽는 동안 센서 쓰기가 집중되면 지연될 수 있어 위 코드처럼 PRAGMA journal_mode = WAL;을 반드시 켠다(7.4.4). MCU와 MPU의 시계는 별개다 — ts는 MPU 쪽 datetime('now')(SQLite 기본 표현식)로 기록해, MCU의 millis() 기준 시각과 섞이지 않도록 한다.

12.1.6 3단계 — 웹 대시보드로 시각화

여기가 트레이드오프의 핵심 지점이다. 두 방식 모두 실전에서 쓸 만하다.

① App Lab WebUI 브릭 — 4.2.8에서 예고한 arduino.app_bricks.web_ui.WebUI를 쓰면 웹서버·프론트엔드 배선을 대부분 대신 해 준다. expose_api()로 REST 엔드포인트를, send_message()로 실시간 push를 만든다.

from arduino.app_bricks.web_ui import WebUI

web_ui = WebUI()

def push_sample(sensor_name, value):
    conn = sqlite3.connect(DB_PATH)
    conn.execute("INSERT INTO sensor_log (sensor_name, value) VALUES (?, ?)", (sensor_name, value))
    conn.commit()
    conn.close()
    web_ui.send_message("sensor_update", {"sensor": sensor_name, "value": value, "ts": time.time()})
    return True

def get_recent(sensor_name: str, limit: str = "100"):
    conn = sqlite3.connect(DB_PATH)
    rows = conn.execute(
        "SELECT value, ts FROM sensor_log WHERE sensor_name = ? ORDER BY id DESC LIMIT ?",
        (sensor_name, int(limit)),
    ).fetchall()
    conn.close()
    return [{"value": r[0], "ts": r[1]} for r in rows]

web_ui.expose_api("GET", "/api/samples/{sensor_name}/{limit}", get_recent)
Bridge.provide("push_sample", push_sample)
App.run()

프런트엔드는 python/assets/(또는 App Lab이 정한 자산 폴더 — 4.2.1 구조 참고)에 페이지를 두면 App Lab이 서빙한다. 실제 접속 URL·포트는 App Lab 콘솔에 표시되는 값을 그대로 쓴다(⚠️ 확인 필요 — 이 책 집필 시점에 고정 포트 번호를 문서에서 직접 확인하지 못했다, 3.3.5 참고).

② 직접 만든 웹앱 — Flask/FastAPI + Chart.js로 완전히 통제된 대시보드를 만든다. App Lab 브릭에 없는 커스텀 쿼리(시간대별 집계, 알람 임계값 표시 등)가 필요하거나, App Lab 없이 순수 systemd 서비스로 실행하고 싶을 때 적합하다.

from flask import Flask, jsonify
app = Flask(__name__)

@app.route("/api/samples/<sensor_name>")
def api_samples(sensor_name):
    conn = sqlite3.connect(DB_PATH)
    rows = conn.execute(
        "SELECT value, ts FROM sensor_log WHERE sensor_name = ? ORDER BY id DESC LIMIT 200",
        (sensor_name,),
    ).fetchall()
    conn.close()
    return jsonify(rows)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

프런트엔드는 fetch("/api/samples/temp_c") 결과를 Chart.js 등으로 그리면 된다(정적 자산은 실 배포 시 CDN보다 로컬 번들을 권장 — 보드가 인터넷과 단절된 현장에 놓일 수 있다).

기준① App Lab WebUI 브릭② 커스텀 Flask/FastAPI 웹앱
초기 개발 속도빠름 — 배선을 브릭이 대신 함느림 — 라우팅·정적 파일 서빙 직접 구현
커스터마이즈 자유도브릭 API 범위 내완전 자유
App Lab 의존성있음(App Lab 런타임 필요)없음 — 순수 Debian 서비스로도 동작
실시간 pushsend_message()로 내장 지원직접 WebSocket/SSE 구현 필요
배포 형태App Lab 프로젝트로 관리systemd 서비스·Docker 등 자유(5.1, 6.3)

견해: 프로토타입·데모 단계에는 ①이 압도적으로 빠르다. 장기 운영·타 시스템 연동(Grafana, MQTT 브로커 등)을 계획한다면 ②로 시작하거나, ①로 만든 뒤 필요해지면 ②로 옮겨가는 편이 현실적이다.

12.1.7 4단계 — Wi-Fi로 원격에서 보기

UNO Q의 Wi-Fi/Bluetooth 콤보 칩(WCBN3536A, 부품 지정번호 U2901)은 MPU(QRB2210) 쪽에 연결되어 있다 — Wi-Fi는 SDIO로, Bluetooth는 UART로 MPU와 연결된다는 것이 이 책 2.1.2.2에서 1차 자료(Arduino User Manual 부품 지정번호)로 확인한 사실이다. 즉 Wi-Fi 연결은 스케치가 아니라 Debian 쪽 NetworkManager(nmcli)로 설정한다(4.4).

같은 LAN에 있으면 http://<uno-q-ip>:<port>/로 대시보드에 접속한다. hostname -I 또는 ip addr로 IP를 확인한다. mDNS(avahi-daemon)로 uno-q.local처럼 접속할 수 있는지는 App Lab의 mDNS 검색 기능(3.3.3, PC-hosted Network 모드)과 별개로 보드 자체에 avahi가 기본 설치돼 있는지 확인이 필요하다(확인 필요).

보안 주의: 위 예제는 인증이 없다. LAN 밖(외부 인터넷)에 노출하려면 포트포워딩을 그대로 열지 말고, ⓐ VPN(WireGuard 등)으로 LAN에 들어온 것처럼 접속하거나 ⓑ Nginx 리버스 프록시 뒤에서 기본 인증·TLS를 추가하는 방법을 권한다. 부록 A.1(트러블슈팅)에 관련 체크리스트를 둔다.

12.1.8 엣지케이스와 트러블슈팅

  • Bridge 재시작 시 데이터 유실: MPU를 재부팅하거나 App Lab 앱을 재배포하면 그 사이의 Bridge.notify()는 받는 쪽이 없어 유실된다. 결측 없는 로깅이 필요하면 MCU 쪽에 소형 순환 버퍼를 두고 재연결 시 재전송하는 로직을 추가한다(간단한 프로젝트에선 과설계일 수 있음 — 트레이드오프로 판단).
  • 센서 I2C 오류: Qwiic 케이블 탈착·노이즈로 thermo.begin()이 실패할 수 있다. setup()에서 반환값을 확인하고 Monitor로 로그를 남겨 SSH(5.2)로 원격 확인한다.
  • 시계 동기화: MPU에서 NTP 동기화를 할 수 없으면(현장에 인터넷이 없으면) datetime('now')가 부팅 시점 기준으로 어긋날 수 있다 — RTC 배터리 유무·timedatectl 상태를 5.5·A.1에서 함께 점검한다.
  • eMMC 수명: 초 단위로 계속 INSERT하면 저가 eMMC의 쓰기 마모가 누적된다. 실서비스라면 WAL 모드 + 배치 커밋(예: 10개 모아 한 번에 쓰기) 또는 tmpfs 버퍼 후 주기적 flush를 고려한다(5.4).
  • 다중 대시보드 접속: 여러 브라우저가 동시에 웹소켓을 열면 send_message() 브로드캐스트 성능에 영향을 줄 수 있다 — 접속자가 많아질 프로젝트라면 ②(커스텀 웹앱) 경로에서 접속자 수 제한이나 폴링 간격 조절을 직접 설계한다.

12.1.9 확장 아이디어

  • 다중 노드 집계: UNO Q 여러 대를 각기 다른 위치에 두고 모두 PostgreSQL 중앙 서버(7.3)로 보내면, 하나의 대시보드에서 기기별로 구분해 시계열을 겹쳐 그릴 수 있다.
  • 알람: push_sample() 안에서 임계값을 넘으면 Bridge로 MCU에 set_led(true) 같은 RPC를 되돌려 보내 현장 LED로 즉시 경고 — Bridge의 양방향성을 활용하는 좋은 예다(3.4.4). 단, 이 콜백이 MCU 쪽 provide()로 등록돼 있다면 그 안에서 다시 Bridge.call()을 부르지 않도록 데드락 함정(3.4.4)을 피해 설계한다.
  • MQTT 연동: SQLite/PostgreSQL 대신(또는 병행) App Lab 공식 저장소(arduino/app-bricks-py)에 공개된 mqtt 브릭(4.2.8)으로 Node-RED·Home Assistant 같은 기존 IoT 스택에 값을 흘려보낼 수도 있다.
  • Zenoh 대체 경로: 여러 UNO Q 사이의 데이터 공유를 RPC 왕복이 아니라 pub/sub로 하고 싶다면 zenoh-pico(7.2)를 MCU에 직접 탑재하는 방식도 검토할 수 있다 — Bridge는 "같은 보드 안"의 MPU-MCU 통신이고, Zenoh는 "보드 간" 통신이라는 역할 차이를 7.2에서 구분해 다룬다.

12.1.10 BOM — 자재명세서

이 절은 12.1.1~12.1.9에서 실제로 등장한 부품·모듈만을 대상으로 정리한 자재명세서(BOM)다. 가격은 변동이 크고 지역·환율·판매 채널에 따라 달라지므로, 이 책이 다른 장(1.1, 1.3)에서 이미 시점을 명시해 확인한 가격만 그대로 인용하고, 그 외에는 확인 필요로 표기한다 — 구매 전 반드시 공식 스토어(store.arduino.cc, store-usa.arduino.cc)나 SparkFun 등 판매 페이지에서 재확인한다.

12.1.10.1 이미 보유하고 있다고 전제하는 것

12.1.1이 전제하는 장(2.1·2.2·3.1 등)에서 다룬, 보드 자체와 최소 동작에 필요한 항목이다. 이 프로젝트만을 위해 새로 살 필요는 없다.

품목참고 장비고
UNO Q 보드(2GB 또는 4GB)1.1이 프로젝트는 특정 모델(메모리 용량)을 요구하지 않는다 — SQLite 로컬 저장만 쓰면 2GB로도 충분하다는 것이 필자의 견해
USB-C 케이블, 5V/3A 이상 전원(또는 45W PD 어댑터)1.3.2최소 동작 필수품, 박스 동봉 여부는 확인 필요(1.3.1)
PC 또는 SBC 모드용 모니터·키보드·마우스1.3.4App Lab 최초 설정·개발용
Wi-Fi 네트워크(2.4/5GHz 공유기)2.1, 4.4대시보드에 원격 접속하는 경로(12.1.7)

12.1.10.2 프로젝트 전용으로 추가 구매할 것

품목수량참고 사양/모델참고 장가격대
Modulino Thermo1개온습도 센서, Qwiic(4핀 JST-SH) 커넥터 내장12.1.4, 10.3.4확인 필요 — Modulino 라인 전체의 정확한 최신 가격은 10.3.9가 이미 "확인 필요"로 표기해 두었다. 구매 전 공식 스토어(store.arduino.cc)에서 재확인
(선택) 그 외 Modulino 센서(Distance, Movement 등)필요 수만큼같은 Qwiic 데이지체인에 추가12.1.4확인 필요(위와 동일)
Qwiic 케이블(4핀 JST-SH)모듈당 1개Modulino 제품에 기본 동봉되는 경우가 많음 — 동봉 여부는 확인 필요10.3.1, 2.3.5별도 구매 시 소액(SparkFun Qwiic 케이블 기준, 확인 필요)
(선택) I2C 멀티플렉서1개동일 종류 Modulino를 여러 개 연결할 때(주소 충돌 회피)12.1.4 엣지케이스확인 필요
(선택) 상태 표시 LED + 저항1개 이상알람 확장(12.1.9)에서 MCU GPIO로 점등 — 코드 예시에는 없어 별도 배선 필요12.1.9소액, 확인 필요
(선택) PostgreSQL 서버용 별도 컴퓨터1대UNO Q가 아닌 별도 서버(또는 기존 PC 재사용) — 신규 구매가 필수는 아님12.1.5, 7.3기존 장비로 대체 가능하면 추가 비용 없음
(선택, 다중 노드 확장) 추가 UNO Q 보드필요 수만큼위치별로 한 대씩12.1.92GB $59.00 / 4GB $79.00 (1.1, 확인 시점 2026-08-24, store-usa.arduino.cc — 이후 변동 가능)

정직성 노트: 위 표의 "확인 필요" 항목은 이 책이 실시간 가격 조회 없이 작성됐기 때문이 아니라, Modulino·Qwiic 액세서리류는 10.3.9에서 이미 "제품 라인이 계속 늘어나는 중이라 정확한 최신 가격은 구매 전 재확인이 필요하다"고 판단한 것을 그대로 이어받은 것이다. 반대로 UNO Q 보드 본체 가격처럼 이 책이 시점을 명시해 직접 확인한 값은 그대로 인용했다(1.1).