약 먹었는지 매번 까먹는다면? 3만원 물리 버튼으로 해결하기 — 코드 한 줄 안 쓴 설계 주차
이번 주는 아무것도 구현하지 않았습니다. 부품도 아직 안 샀고요.
대신 AI와 계속 대화하면서 "무엇을 만들지"를 좁혀갔는데, 그 과정에서 제가 처음에 세웠던 가정이 여러 번 깨졌습니다. 그 깨지는 과정이 오히려 남길 가치가 있다고 생각해서, 결과물 대신 의사결정 로그를 정리합니다.
1. 배경 — 이미 있는 시스템에 뭘 더 붙이려는 건가
몇 주 전부터 집에 이런 걸 돌리고 있습니다.
텔레그램 (부부 둘) → 맥미니 에이전트 → Supabase → 거실 태블릿
"우유 사왔어", "금요일 3시 병원 예약" 같은 걸 텔레그램에 던지면 맥미니에서 도는 에이전트가 알아서 분류하고, 챙길 게 생기면 거실 태블릿에 카드로 뜹니다.
잘 쓰고 있는데, 약 복용만 유독 기록이 안 됐습니다.
이유가 명확했어요. 약은 먹는 데 3초 걸리는데, 폰을 꺼내서 텔레그램을 열고 "약 먹었어"를 치는 게 10초입니다. 그래서 안 하게 되더라고요. 기록의 가치보다 기록의 비용이 큰 겁니다.
그래서 결론: 누르는 데 0.5초 걸리는 물리 버튼이 필요하다.
---
2. 첫 질문 — "이걸 어떻게 만들죠?"가 아니었다
처음 AI에게 던진 건 설계 문서였습니다. "이런 걸 만들고 싶은데 어떻게 생각해?"가 아니라, 제가 먼저 초안을 쓰고 검증을 요청했습니다.
이 방식이 좋았던 게, 대화가 "뭘 만들까"가 아니라 "이 설계의 약한 곳이 어디냐"로 시작됐습니다. 그 결과 나온 원칙 세 가지가 이번 주 결론의 대부분입니다.
원칙 1: 펌웨어는 최대한 멍청하게
ESP32는 버튼을 읽고, 요청을 보내고, 응답을 화면에 그리는 것까지만 합니다. 하루 경계가 언제인지, 중복인지, 리마인드를 보낼지는 전부 서버가 판단합니다.
이유는 하나입니다. 펌웨어 재플래싱은 귀찮습니다. 로직이 펌웨어에 들어가면 "리마인드를 9시에서 8시로 바꾸자"에도 USB를 꽂아야 합니다. 서버에 있으면 코드 한 줄 고치고 재시작이면 끝입니다.
그래서 화면에 뿌릴 문구조차 서버가 내려줍니다.
{
"status": "recorded",
"display": {
"title": "오늘 완료 · 09:14",
"subtitle": "이번 주 6/7일",
"color": "green"
}
}
ESP32는 이 JSON을 받아서 color대로 배경을 칠하고 title을 그릴 뿐입니다.
원칙 2: 시각의 근거는 age_ms
ESP32에는 RTC가 없어서 전원이 나가면 시계를 잃습니다. 그래서 이벤트에 "몇 시에 눌렀다"가 아니라 "눌린 지 몇 ms 됐다"를 담습니다.
{ "event_id": "med-button-01:7:42", "type": "taken", "age_ms": 1830 }
서버가 실제_시각 = 수신_시각 - age_ms로 복원합니다. NTP가 안 붙었든, 오프라인 큐에 3시간 쌓여 있다가 나중에 전송되든 시각이 정확합니다.
원칙 3: 취소를 DELETE로 처리하지 않는다
이벤트 로그는 append-only입니다. 취소하면 cancelled 이벤트를 추가로 쌓고, 현재 상태는 로그를 재생해서 계산합니다.
med_events (절대 UPDATE/DELETE 안 함)
event_id TEXT PRIMARY KEY -- 멱등성 키
type TEXT -- taken | cancelled
occurred_at TIMESTAMP -- received_at - age_ms
source TEXT -- button | dashboard | agent
이러면 "몇 번 잘못 눌렀다가 취소했는지"까지 남아서 나중에 분석 재료가 됩니다. 그리고 append-only는 멱등성과 궁합이 좋습니다.
한 가지 더 — 하루 경계는 자정이 아니라 새벽 4시로 잡았습니다. 23:55에 먹고 5분 뒤 날짜가 넘어가면 "오늘 안 먹음"이 되는 게 위험해서요.
---
3. 아키텍처
```
[60mm 아케이드 버튼]
│ GPIO / GND 2가닥
[ESP32] │ 버튼 읽고 HTTP 전송, USB-C 전원
│ WiFi
[집 공유기] 같은 랜 · 외부 노출 없음
│ HTTP POST /api/med/event
[맥미니] 에이전트 + HTTP 서버
│ 중복 · 4시 경계 판정
[Supabase] med_events 이벤트 로그
│
[태블릿 · 폰] 대시보드 · 텔레그램
← 응답 JSON (색 · 문구) 은 맥미니가 ESP32로 되돌려줌
```
엔드포인트는 2개면 충분하다는 결론이었습니다.
- POST /api/med/event — 이벤트 기록 (멱등성 키로 중복 제거)
- GET /api/med/state — 5분마다 폴링해서 화면 최신화 (태블릿에서 수동 기록해도 버튼 화면이 따라옴)
버튼과 태블릿 대시보드는 같은 API를 쓰는 대등한 클라이언트입니다. 어느 쪽에서 기록해도 반대쪽에 반영됩니다.
---
4. 부품 고르다 깨진 가정 4개
여기가 이번 주의 진짜 내용입니다. 설계는 30분이면 나왔는데, 부품 고르는 데 몇 시간이 걸렸고 그 과정에서 제 가정이 계속 틀렸습니다.