제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 무엇을 만드나 — 전체 파이프라인
읽는 순서: ① 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 서비스로도 동작 |
| 실시간 push | send_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.4 | App Lab 최초 설정·개발용 |
| Wi-Fi 네트워크(2.4/5GHz 공유기) | 2.1, 4.4 | 대시보드에 원격 접속하는 경로(12.1.7) |
12.1.10.2 프로젝트 전용으로 추가 구매할 것
| 품목 | 수량 | 참고 사양/모델 | 참고 장 | 가격대 |
|---|---|---|---|---|
| Modulino Thermo | 1개 | 온습도 센서, 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.9 | 2GB $59.00 / 4GB $79.00 (1.1, 확인 시점 2026-08-24, store-usa.arduino.cc — 이후 변동 가능) |
정직성 노트: 위 표의 "확인 필요" 항목은 이 책이 실시간 가격 조회 없이 작성됐기 때문이 아니라, Modulino·Qwiic 액세서리류는 10.3.9에서 이미 "제품 라인이 계속 늘어나는 중이라 정확한 최신 가격은 구매 전 재확인이 필요하다"고 판단한 것을 그대로 이어받은 것이다. 반대로 UNO Q 보드 본체 가격처럼 이 책이 시점을 명시해 직접 확인한 값은 그대로 인용했다(1.1).