# Plan: Balcony Weather Station Dashboard ## 1. Project Overview A real-time, neighborhood-facing weather dashboard that streams sensory data from an MQTT broker (Home Assistant) to a web interface. ### Tech Stack - **Backend:** Gleam (Target: Erlang/OTP) - **Web Server:** Mist (HTTP & WebSockets) - **Database:** SQLite (via `sqlight`) for persistent uptime tracking - **Frontend:** SolidJS (Signals-based reactivity) + Tailwind CSS - **Infrastructure:** Nomad (Docker-based deployment) --- ## 2. Core Architecture & Actors - **MQTT Actor:** Subscribes to weather topics (e.g., `tele/sensor/SENSOR`), parses JSON, and broadcasts updates. - **Monitor Actor:** Tracks sensor "heartbeats." Maintains a state of "Last Seen." - **WebSocket Manager:** Handles browser connections; pushes HTML-ready JSON to SolidJS. - **Storage:** Single `weather.db` file using "Schema-on-Boot" (no manual migrations). --- ## 3. Implementation Phases ### Phase 1: Backend Infrastructure (Gleam) - [ ] Initialize Gleam project with `gleam_otp`, `mist`, `wisp`, and `sqlight`. - [ ] Implement `database.gleam`: - Function `init_db` to run `CREATE TABLE IF NOT EXISTS sensor_stats`. - Schema: `sensor_id (PK)`, `last_seen (DATETIME)`, `uptime_start (DATETIME)`. - [ ] Implement `mqtt_handler.gleam`: - Use `gemqtt` (wrapper for `emqtt`) to connect to the broker. - Map incoming payloads to a `WeatherUpdate` custom type. ### Phase 2: Uptime Logic - [ ] Implement the `Monitor` Actor logic: - On MQTT message: Update `last_seen` in SQLite. - If `(CurrentTime - last_seen) > 10 minutes`, consider the sensor "disrupted." - On recovery: Reset `uptime_start` to `now`. - [ ] Calculate "Days since disruption" using: $$(CurrentTime - UptimeStart) / 86400$$ ### Phase 3: Real-time Streaming - [ ] Set up a Mist WebSocket handler. - [ ] Use a simple process registry (or `gleam_otp` subject) to broadcast JSON to all connected clients. - [ ] Ensure the JSON payload includes: `temp`, `humidity`, `wind`, and `uptime_days`. ### Phase 4: Frontend (SolidJS) - [ ] Scaffold SolidJS via Vite. - [ ] Create a `WeatherCard` component. - [ ] Implement `createEffect` for WebSocket management: - On `ws.onmessage`: Update Solid signals. - On `ws.onclose`: Implement simple exponential backoff for reconnection. - [ ] Style with Tailwind CSS (Card-based layout, dark mode). ### Phase 5: Deployment (Nomad) - [ ] Create a multi-stage `Dockerfile`: - Build SolidJS assets. - Compile Gleam to a production Erlang release. - [ ] Create `weather.nomad` job file: - Mount `host_volume` for the `.db` file. - Configure networking for the WebSocket port (default 8080). --- ## 4. Claude Code Implementation Instructions 1. **Start with the Backend:** Focus on the `WeatherUpdate` type and the MQTT subscriber first. 2. **Database:** Use `sqlight.exec` to ensure the table exists on every app start. 3. **Frontend:** Keep the SolidJS app in a `./frontend` subdirectory. 4. **Mocking:** If the MQTT broker isn't reachable during coding, create a "Simulator" actor that sends random weather data every 5 seconds.