{"id":51071882,"url":"https://github.com/tlugger/wtwlt","last_synced_at":"2026-06-23T11:02:47.858Z","repository":{"id":365364150,"uuid":"1271750104","full_name":"tlugger/wtwlt","owner":"tlugger","description":"What's the weather, what's the weather, what's the weather like today? Is it sunny? Is it rainy? Is it windy out today?","archived":false,"fork":false,"pushed_at":"2026-06-17T03:08:11.000Z","size":64,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T04:11:37.903Z","etag":null,"topics":["esp32","iot","mqtt","weather-station"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tlugger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-17T01:28:31.000Z","updated_at":"2026-06-17T03:08:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tlugger/wtwlt","commit_stats":null,"previous_names":["tlugger/wtwlt"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/tlugger/wtwlt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlugger%2Fwtwlt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlugger%2Fwtwlt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlugger%2Fwtwlt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlugger%2Fwtwlt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tlugger","download_url":"https://codeload.github.com/tlugger/wtwlt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlugger%2Fwtwlt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34686726,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esp32","iot","mqtt","weather-station"],"created_at":"2026-06-23T11:02:42.611Z","updated_at":"2026-06-23T11:02:47.851Z","avatar_url":"https://github.com/tlugger.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wtwlt — What's The Weather Like Today\n\nA self-hosted home weather station, built as a **monorepo**. An outdoor,\nsolar-powered ESP32 sensor node publishes readings over MQTT to a Raspberry Pi,\nwhich logs them to SQLite and serves an earth-toned dashboard + JSON API.\n\n- **`firmware/`** — ESP32 firmware for the sensor node (PlatformIO). Samples the\n  sensor suite at 1 Hz, aggregates over a 60-second window, and publishes\n  metric/SI readings to an MQTT broker. Lightning strikes are published as they\n  happen. See [`firmware/README.md`](firmware/README.md).\n- **`server/`** — the Raspberry Pi backend: a single **Go service** that ingests\n  the station's MQTT messages into SQLite and serves the dashboard + JSON API from\n  the same binary. Ships with a local Mosquitto config and a Python mock publisher\n  for exercising the pipeline without hardware. See [`server/README.md`](server/README.md).\n\n## Architecture\n\n```\n┌─────────────────────────┐         MQTT/WiFi         ┌──────────────────────────────┐\n│  Weather Station Node    │ ───────────────────────▶ │  Raspberry Pi                  │\n│  ESP32 + SparkFun        │   (Mosquitto broker on    │                                │\n│  MicroMod Weather        │    the Pi)                │  • Mosquitto broker            │\n│  Carrier Board           │                           │  • Go service: MQTT ingest →   │\n│                          │                           │    SQLite → dashboard / API    │\n│  Solar + battery, always │                           │    (public exposure via        │\n│  awake, publishes 1×/min │                           │     port-forward + DDNS +       │\n│                          │                           │     reverse proxy)             │\n└─────────────────────────┘                           └──────────────────────────────┘\n```\n\n**Data flow:** sensors → ESP32 samples @1 Hz → aggregates over 60 s → publishes\none JSON message per minute (plus event-driven lightning) to MQTT → the Go\nservice on the Pi subscribes, persists to SQLite (downsampling old data into\nhourly/daily rollups), and serves the dashboard + API that read it.\n\n## Hardware\n\nAn **ESP32** (MicroMod form factor) on a **SparkFun MicroMod Weather Carrier**:\n\n- **BME280** (I²C) — temperature, humidity, barometric pressure\n- **VEML6075** (I²C) — UV index\n- **AS3935** (SPI) — lightning detection (strike distance + energy)\n- **SparkFun Weather Meter Kit** — anemometer, wind vane, tipping-bucket rain\n  gauge (driven by the `SFEWeatherMeterKit` library)\n- **Analog soil moisture** probe on the carrier's terminal\n- **Power:** solar panel + LiPo, continuously awake (battery voltage reported in\n  diagnostics)\n\n**Pin map** (ESP32 MicroMod Processor on the Weather Carrier):\n\n| Signal | Pin | Bus / notes |\n|--------|-----|-------------|\n| Wind direction (vane) | GPIO 35 (A1) | ADC1 — WiFi-safe analog |\n| Wind speed (anemometer) | GPIO 23 (D0) | digital, interrupt |\n| Rain gauge | GPIO 27 (D1) | digital, interrupt |\n| Soil moisture | GPIO 39 (A0) | ADC1 analog terminal |\n| AS3935 lightning | CS = GPIO 12 | SPI; INT pin to verify on the bench |\n| BME280 / VEML6075 | I²C | 0x77 / 0x10 |\n\nPins, cadence, and calibration constants live in\n[`firmware/include/config.h`](firmware/include/config.h).\n\n## Dashboard\n\nAn earth-toned dashboard whose palette shifts with the time of day and live\nconditions (clear / dusk / night / rain / storm):\n\n![wtwlt dashboard shown in day, dusk, and night themes](docs/dashboard-themes.png)\n\n## MQTT data contract\n\nThe node publishes metric/SI values to the Mosquitto broker on the Pi (QoS 1;\nretained LWT for status). `\u003cstation_id\u003e` defaults to `wtwlt-01`.\n\n| Topic | Purpose | Cadence |\n|-------|---------|---------|\n| `wtwlt/station/\u003cstation_id\u003e/readings` | aggregated sensor readings | every 60 s |\n| `wtwlt/station/\u003cstation_id\u003e/lightning` | lightning strike events | on event |\n| `wtwlt/station/\u003cstation_id\u003e/status` | online/offline + identity (retained, LWT) | on connect/disconnect |\n\n**`readings`** — absent sensors are emitted as `null`:\n\n```json\n{\n  \"station_id\": \"wtwlt-01\",\n  \"ts\": \"2026-06-16T12:00:00Z\",\n  \"interval_s\": 60,\n  \"temp_c\": 21.4,\n  \"humidity_pct\": 58.2,\n  \"pressure_hpa\": 1013.2,\n  \"uv_index\": 3.1,\n  \"wind\": { \"avg_mps\": 2.4, \"gust_mps\": 5.1, \"dir_deg\": 270, \"dir_cardinal\": \"W\" },\n  \"rain_mm\": 0.5,\n  \"soil_moisture_pct\": 42.0,\n  \"diagnostics\": { \"battery_v\": 3.92, \"rssi_dbm\": -67, \"uptime_s\": 38211, \"fw_version\": \"1.0.0\" }\n}\n```\n\n**`lightning`** — `event` ∈ `strike` | `disturber` | `noise` (only `strike` by default):\n\n```json\n{ \"station_id\": \"wtwlt-01\", \"ts\": \"2026-06-16T12:00:03Z\", \"event\": \"strike\", \"distance_km\": 12, \"energy\": 158473 }\n```\n\n**`status`** — retained; the LWT flips `online` to `false` on disconnect:\n\n```json\n{ \"station_id\": \"wtwlt-01\", \"online\": true, \"fw_version\": \"1.0.0\", \"ip\": \"192.168.1.42\", \"boot_ts\": \"2026-06-16T01:23:45Z\" }\n```\n\nTimestamps are UTC (the ESP32 syncs via SNTP; the server stamps arrival time if\nthe node's clock isn't set). Everything is stored metric; the API converts to\nimperial on request via a `units=metric|imperial` query param.\n\n## Build \u0026 flash the firmware\n\nThis repo uses [`just`](https://github.com/casey/just) as a task runner. Firmware\nrecipes are namespaced under `firmware`:\n\n```bash\njust firmware secrets   # create firmware/include/secrets.h from the template, then edit it\njust firmware test      # host unit tests (no board needed)\njust firmware build     # compile for the ESP32\njust firmware flash     # flash over USB  (append /dev/cu.usbserial-XXXX for a specific port)\njust firmware dev       # flash, then open the serial monitor\n```\n\nRun `just` with no arguments to list recipes and modules. Full instructions and\nverification steps are in [`firmware/README.md`](firmware/README.md).\n\n## Run the server pipeline (no hardware)\n\nExercise MQTT → Go ingest → SQLite → API end-to-end (requires `brew install mosquitto`):\n\n```bash\njust server broker   # terminal 1: start Mosquitto\njust server run      # terminal 2: start the Go service (ingests -\u003e SQLite, serves API)\njust server setup    # terminal 3: one-time, create the mock venv\njust server mock     # terminal 3: publish mock readings/lightning/status\n```\n\nThen open the dashboard at \u003chttp://localhost:8080/\u003e, or `curl localhost:8080/api/current`.\nDetails in [`server/README.md`](server/README.md).\n\n## Deploy to the Raspberry Pi\n\nCut a release from the GitHub **Actions → release** workflow (it cross-compiles\nthe server for `linux/arm64·amd64·arm` and publishes a GitHub Release). Then, on\nthe Pi:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/tlugger/wtwlt/main/install.sh | sudo bash\n```\n\nThe installer picks the right binary for the Pi's architecture, installs a\n`systemd` service, and starts it. Re-running it upgrades in place. Config lives in\n`/home/pi/wtwlt/.env`. Details in [`server/README.md`](server/README.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftlugger%2Fwtwlt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftlugger%2Fwtwlt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftlugger%2Fwtwlt/lists"}