PowerBot: Telegram Power Status Bot with ESP32 Heartbeats and Read-Only Status API
Docker-deployed Telegram bot infers building power status from ESP32 heartbeats, stores in SQLite, notifies users, and exposes a read-only HTTP status API.
Overview
PowerBot is a Telegram bot stack that tracks “is power up” per building/section using periodic heartbeat POSTs from ESP32 sensors. It stores state in SQLite, notifies subscribers in Telegram, and also exposes HTTP endpoints (health, sensor status, public read-only status) behind a reverse proxy.
Main Content
Inputs
- ESP32 sensors send
POST /api/v1/heartbeatwith{api_key, building_id, section_id, sensor_uuid, ...}. - Telegram users interact with the bot via polling (commands, callbacks, menus).
- Optional external data sources appear wired in via configuration (weather coordinates, alert APIs, planned outage schedules).
Processing (end-to-end flow)
- Heartbeat validation: the API server checks the sensor API key and basic payload types.
- Canonical mapping:
sensor_uuidcan override a reportedbuilding_idso firmware-side IDs cannot silently drift the backend’s “source of truth.” - Section normalization: missing/invalid
section_idcan be defaulted, with stricter handling depending on whether the sensor is in the canonical map. - Persistence: the heartbeat is upserted into SQLite (sensor record + last heartbeat timestamp).
- Status inference: a sensor is considered online if
last_heartbeatis recent; building/section “power up” becomes true if any sensor (or virtual alias) is effectively online. - Monitoring loop: the bot runtime starts background tasks for sensor monitoring, alert monitoring, optional outage schedule monitoring, and an admin job worker.
Outputs
- Telegram UI updates: users see current power status (and other menu items), and subscribers can receive notifications.
- Public read-only status API:
GET /api/v1/public/sensors/statusand per-sensor status provide external consumption without revealing the write key. - Admin/business sidecars (optional): separate bot entrypoints can be started via Docker Compose profiles.
System Context
Role
- Resident-facing channel: a Telegram bot that answers “is power up” (and related context) for a building/section.
- Data-plane ingestion: an HTTP API that receives sensor heartbeats and updates state.
- External integration surface: a read-only API for third parties to query sensor status.
Boundary
- Assumes sensors can reach the HTTP endpoint and that Telegram polling is permitted from the host environment.
- Treats “power up” as a proxy inferred from sensor presence/heartbeat recency, not direct voltage measurement semantics in the backend itself.
Interfaces
- Sensor -> API:
POST /api/v1/heartbeatwith shared secret key. - Client -> Public API:
GET /api/v1/public/sensors/statusauthenticated with a separate read-only key (header/bearer/query supported). - Operations -> Stack: Docker Compose services plus reverse-proxy labels (Traefik).
- Storage: SQLite database mounted as a shared volume for multiple services (main bot, business bot, admin bot, migrations).
Architecture / Design Considerations
- Multi-process, single-DB choice: several services share one SQLite file, with an explicit volume mount to keep WAL/SHM alongside the DB for consistency across containers. This is simple to deploy but increases sensitivity to write contention and file-lock behavior.
- “Canonical mapping” as risk control: binding
sensor_uuid -> building_idinside backend config reduces the blast radius of misconfigured firmware IDs and prevents a single rogue payload from reassigning a sensor to the wrong building. - Aliasing as a pragmatic bridge: “virtual sensor” aliasing lets one physical signal represent multiple sections/buildings (shared lines). It improves user experience but can make history/statistics ambiguous unless the mapping is one-to-one for a given target.
- Feature-flag separation: business functionality is gated by environment flags and can run as a separate bot runtime; this reduces accidental impact on resident UX during rollout but adds operational surface area (more tokens, more bots, more states).
Possible Implications
- Operational: if SQLite locking appears under load (broadcasts, frequent heartbeats, admin jobs), notifications and status freshness can degrade. The presence of broadcast throttling knobs hints that tuning is expected in real deployments.
- Integration: third parties can query status without write credentials, but key management becomes a real responsibility (separate write vs read-only keys).
- Verification: correctness depends heavily on heartbeat cadence, timeout settings, and mapping tables; testing needs to cover missing/invalid sections and “moved sensor” scenarios.
Conclusion
PowerBot’s core is straightforward: accept authenticated ESP32 heartbeats, infer “up/down” by recency, and present it through Telegram plus a read-only HTTP API. The first things worth validating are (1) sensor-to-building/section mapping behavior under misreports and (2) SQLite contention behavior when broadcasts, heartbeats, and admin jobs overlap.
전체 개요
본 저장소는 ESP32 센서의 heartbeat 수신을 기반으로 건물 및 섹션 단위의 “전기 공급 상태”를 추정하고, 이를 Telegram 봇 UI와 HTTP 읽기 전용 API로 제공하는 시스템으로 보입니다. 배포는 Docker Compose를 중심으로 구성되어 있으며, 역프록시(Traefik) 라우팅과 SQLite 단일 DB 공유를 전제로 하는 형태입니다.
배경과 목적
이 시스템의 목적은 “주민이 체감하는 유틸리티 상태(특히 전기)”를 빠르게 공유하는 데 있는 것으로 해석될 여지가 있습니다. 직접 계측값을 정교하게 처리하기보다는, 센서가 일정 주기로 살아있음을 알리는 heartbeat의 최신성을 기준으로 상태를 단순화하여 전달하는 접근을 택한 것으로 보입니다.
기술 흐름 설명 (신호/데이터/동작 순서 중심)
- ESP32 센서가 HTTP로
POST /api/v1/heartbeat요청을 전송합니다. payload에는api_key,building_id,section_id,sensor_uuid등이 포함됩니다. - 서버는
api_key를 비교하여 인증을 수행하고,building_id/sensor_uuid타입을 검증합니다. sensor_uuid기반의 “정규화 매핑”이 적용될 수 있으며, 이 경우 센서가 보고한building_id가 서버 기준 값으로 덮어써질 수 있습니다. 이는 센서 측 설정 실수로 인한 상태 오염을 줄이려는 의도로 보입니다.section_id가 누락되거나, 건물에 대해 유효하지 않으면 기본 섹션으로 보정하거나 오류 처리합니다(조건에 따라 달라질 수 있습니다).- DB에 센서 정보 및 마지막 heartbeat 시각이 upsert 형태로 저장됩니다.
- 봇 런타임은 백그라운드 모니터링 루프를 통해 heartbeat 최신성을 기준으로 센서 online/offline을 판정하고, “해당 건물/섹션에 online 센서가 하나라도 있으면 전기 공급 중” 같은 방식으로 상태를 추정하는 것으로 보입니다.
- 사용자는 Telegram에서 메뉴/상태 화면을 통해 정보를 확인하며, 구독자에게는 알림이 발송될 수 있습니다.
- 외부 개발자/시스템을 위해 읽기 전용 상태 API가 제공되며, 쓰기 키를 공개하지 않도록 별도의 read-only 키를 사용합니다.
왜 이런 구조가 나왔는지에 대한 해설
- 단일 DB(SQLite) + Docker 구성은 초기 구축과 운영 단순화를 우선한 선택으로 보입니다. 다만 여러 컨테이너가 같은 DB 파일을 공유하므로, 트래픽이 늘거나 브로드캐스트가 빈번해질수록 lock 경합이 운영 리스크가 될 수 있습니다.
sensor_uuid -> building_id정규화 매핑은 “현장 펌웨어 설정이 바뀌거나 잘못 보고해도 서버 기준의 소스 오브 트루스를 유지”하려는 안전장치로 해석될 여지가 있습니다. 이는 잘못된 상태 전파의 실패 비용을 줄이는 방향입니다.- 섹션/건물 alias 개념은 하나의 물리 센서가 여러 섹션을 대표해야 하는 상황(전원 라인 공유 등)을 임시로 메우기 위한 장치로 보입니다. 대신 히스토리/통계가 모호해질 수 있어, 단일 출처로 귀결되는 매핑인지가 중요해 보입니다.
- 비즈니스/어드민 봇을 분리하고 feature flag로 격리한 점은, 주민용 흐름에 영향을 최소화하면서 기능을 확장하려는 운영 전략으로 볼 수 있습니다.
생소한 개념에 대한 풀어쓴 설명
- Heartbeat 기반 상태 추정: 센서가 “주기적으로 살아있다”는 신호를 보내면 online으로 간주하고, 그 최신성이 일정 시간(타임아웃) 이상 오래되면 offline으로 간주하는 방식입니다.
- Read-only API 키 분리: 센서가 쓰는 키(쓰기 권한)와 외부 조회용 키(읽기 전용)를 분리하여, 조회 기능을 공개하더라도 센서 상태를 위조하는 위험을 낮춥니다.
- Canonical mapping: 센서가 보고한 값보다 서버 설정의 매핑을 우선하여, 센서 측 값이 흔들려도 데이터가 한 곳으로 모이게 만드는 정규화 장치입니다.
시스템 구성 및 선택지 해석
- 역할과 경계
- 시스템이 책임지는 것: 센서 heartbeat 수신, 상태 추정 로직, DB 저장, Telegram 알림/표시, 읽기 전용 조회 API 제공입니다.
- 시스템이 외부에 위임하는 것: 센서 네트워크 연결성, heartbeat 주기 준수, 역프록시/도메인 라우팅, Telegram 플랫폼 자체의 메시징 채널입니다.
- 입력/출력/연계
- 입력: HTTP heartbeat, Telegram 사용자 입력, 환경변수 기반 설정, (구성상) 외부 API 기반 부가 정보(날씨/알림/정전 일정 등)일 수 있습니다.
- 출력: Telegram 화면/알림, 공용 상태 조회 API 응답입니다.
- 선택지(대안 가능성)
- 저장소 관점에서는 SQLite를 유지하되 브로드캐스트/heartbeat 빈도를 조절하는 쪽으로 운영 튜닝이 가능해 보입니다.
- 규모가 커질 경우에는 DB를 분리하거나(예: 별도 DBMS) 쓰기 경합을 줄이는 구조가 필요할 가능성이 있습니다. 이는 코드상으로는 확정하기 어렵고, 운영 조건에 따라 달라질 수 있습니다.
내부 관점에서의 시사점
- 실패 비용이 큰 판단 지점은 “센서 매핑(건물/섹션)과 키 관리”로 보입니다. 매핑이 어긋나면 잘못된 건물에 상태가 표시될 수 있고, 키가 유출되면 heartbeat 위조가 가능해집니다.
- 다중 컨테이너가 단일 SQLite 파일을 공유하므로, 이벤트 폭주나 대량 알림 시점에 “database is locked” 류의 문제가 운영 리스크가 될 수 있습니다. 브로드캐스트 관련 rate/concurrency 설정이 존재하는 점은 이를 완화하려는 의도가 있었을 가능성이 있습니다.
- 문서와 코드의 정합성 측면에서는, README가 배포/운영 중심이고 기능 정의는 코드/스키마에 더 많이 담겨 있는 형태로 보입니다. 운영자가 기능 확장 시 문서 보강을 하지 않으면 인수인계 비용이 커질 수 있습니다.
FAQ
- 이 시스템에서 “전기가 들어온다”는 판단은 무엇을 의미합니까?
센서의last_heartbeat가 타임아웃 이내이면 online으로 간주하고, 건물/섹션에 online 센서가 하나라도 있으면 전기 공급 중으로 추정하는 방식으로 보입니다. 물리 전압을 직접 표현하기보다는 “센서가 살아있다”를 대리 지표로 쓰는 형태입니다. - 왜
sensor_uuid기반의 건물 매핑이 필요한가요?
센서가 보고하는building_id가 현장에서 잘못 설정되거나 바뀌는 경우, 서버가 이를 그대로 수용하면 상태가 다른 건물로 섞일 수 있습니다.sensor_uuid -> building_id정규화는 이런 실패 비용을 낮추려는 안전장치로 해석될 여지가 있습니다. - 기존 접근 대비 차별 지점은 무엇인가요?
읽기 전용 상태 API 키를 별도로 두고, 센서 쓰기 키를 노출하지 않으면서 외부 조회를 허용하는 구조가 눈에 띕니다. 또한 비즈니스/어드민 봇을 분리 런타임으로 둘 수 있어, 주민용 흐름을 보호한 채 기능을 확장하려는 방향이 보입니다. - 구조/아키텍처 선택에서 가장 큰 트레이드오프는 무엇입니까?
여러 서비스가 하나의 SQLite DB를 공유하는 구성은 배포가 단순하지만, 동시 쓰기 부하가 커질수록 lock 경합 리스크가 커질 수 있습니다. 반대로 별도 DBMS로 옮기면 운영 복잡도는 증가할 수 있습니다. - 시스템 연계 또는 통신 역할은 어떻게 나뉘나요?
센서 연계는 HTTP heartbeat 엔드포인트로, 외부 개발자 연계는 읽기 전용 status API로 분리되어 있습니다. 사용자 채널은 Telegram 봇 폴링 기반이며, 외부 트래픽 라우팅은 역프록시 라벨 구성에 의존하는 형태로 보입니다. - 실패 비용이 가장 큰 판단 지점은 어디로 보이나요?
키 유출(센서 쓰기 키 또는 공용 조회 키의 오남용)과 센서 매핑 오류가 가장 큰 리스크로 보입니다. 이 두 영역은 “상태 위조” 또는 “잘못된 건물에 상태 표시”로 직접 이어질 수 있습니다.
저자 정보
- Samuel Morgan
공개된 정보가 제한적임
