{"id":48799797,"url":"https://github.com/cngarrison/zephyr","last_synced_at":"2026-04-14T01:30:42.111Z","repository":{"id":350614620,"uuid":"1207486700","full_name":"cngarrison/zephyr","owner":"cngarrison","description":"Lightweight Deno/TypeScript weather station system — collects, stores and displays PWS data. Modern alternative to weewx.","archived":false,"fork":false,"pushed_at":"2026-04-11T08:21:31.000Z","size":323,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-11T08:23:44.743Z","etag":null,"topics":["deno","meteorology","personal-weather-station","pws","typescript","weather","weather-station","weewx"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/cngarrison.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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-04-11T02:09:33.000Z","updated_at":"2026-04-11T08:21:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cngarrison/zephyr","commit_stats":null,"previous_names":["cngarrison/zephyr"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/cngarrison/zephyr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cngarrison%2Fzephyr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cngarrison%2Fzephyr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cngarrison%2Fzephyr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cngarrison%2Fzephyr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cngarrison","download_url":"https://codeload.github.com/cngarrison/zephyr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cngarrison%2Fzephyr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31778580,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T00:11:49.126Z","status":"ssl_error","status_checked_at":"2026-04-14T00:10:29.837Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["deno","meteorology","personal-weather-station","pws","typescript","weather","weather-station","weewx"],"created_at":"2026-04-14T01:30:41.335Z","updated_at":"2026-04-14T01:30:42.084Z","avatar_url":"https://github.com/cngarrison.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"assets/logo-dark.svg\"\u003e\n    \u003cimg src=\"assets/logo.svg\" alt=\"Zephyr\" width=\"200\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/cngarrison/zephyr/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/cngarrison/zephyr?style=flat-square\" alt=\"GitHub Release\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/cngarrison/zephyr?style=flat-square\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Deno-2.x-black?style=flat-square\u0026logo=deno\u0026logoColor=white\" alt=\"Deno 2.x\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square\u0026logo=typescript\u0026logoColor=white\" alt=\"TypeScript strict\"\u003e\n\u003c/p\u003e\n\n# Zephyr Weather\n\nA lightweight, engine-first weather station system built with Deno and TypeScript. Zephyr collects, stores and displays weather data from personal weather stations — designed as a modern, extensible alternative to [weewx](https://weewx.com).\n\n## Why Zephyr?\n\nweewx is a capable system but has accumulated complexity over many years of Python 2→3 migration, and its storage layer has known issues with MySQL case-sensitivity that emerged in v5.3. Zephyr takes a fresh approach:\n\n- **Engine-first**: the data collection and storage core is the product. The web UI is one consumer among many.\n- **No Python**: pure Deno + TypeScript throughout.\n- **Simple by default**: SQLite out of the box, no database server required.\n- **Extensible by design**: storage adapters, ingest plugins, and sensor types are all interface-driven.\n\n## Features\n\n### Current\n\n- Personal weather station support via WU push, Ecowitt push, and LAN API poll (currently: Ecowitt GW-series gateways)\n- Plugin-based storage system — SQLite (default) and MySQL adapters\n- Auto-normalises all data to SI units internally\n- Broad sensor support: temperature, humidity, pressure, wind, rain, solar, UV\n- Extended sensor support: soil moisture/temperature (8 channels), extra temp/humidity (8 channels), leaf wetness, lightning\n- REST API for observations and station config\n- Fresh v2 web dashboard with current conditions and weather-icon condition cards\n- Today stats: daily high/low and totals on the dashboard\n- Aggregate views: yesterday, week, month, year (Apache ECharts)\n- History heatmaps: temperature, rain and UV heatmaps by day across all available years\n- Almanac: sunrise/sunset times, day length, and moon phase\n- Tailwind v4 dark theme\n- weewx data migration script\n\n### Planned\n\n- Live updates via SSE (currently polling)\n- Third-party embeds (Windy Map, BOM radar, etc.)\n- Cloud upload adapters (Weather Underground, Ecowitt, CWOP)\n- Static HTML export for HomeAssistant integration\n- Themes / skins\n\n## Screenshots\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/screenshots/current.png\" alt=\"Current conditions – light\" width=\"480\"\u003e\u003cbr\u003e\u003csub\u003eCurrent conditions · Light theme\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/screenshots/current-dark.png\" alt=\"Current conditions – dark\" width=\"480\"\u003e\u003cbr\u003e\u003csub\u003eCurrent conditions · Dark theme\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/screenshots/graphs-last24.png\" alt=\"24-hour charts\" width=\"480\"\u003e\u003cbr\u003e\u003csub\u003e24-hour charts\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003cimg src=\"assets/screenshots/history.png\" alt=\"History heatmaps\" width=\"480\"\u003e\u003cbr\u003e\u003csub\u003eHistory heatmaps \u0026amp; records\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd colspan=\"2\" align=\"center\"\u003e\u003cimg src=\"assets/screenshots/almanac.png\" alt=\"Almanac\" width=\"480\"\u003e\u003cbr\u003e\u003csub\u003eAlmanac — sunrise, sunset \u0026amp; moon phase\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## Architecture\n\n```\nGateway ──push──▶ Engine (HTTP :8080)\n                  ├── /ingest/wu          WU protocol push\n                  ├── /ingest/ecowitt     Ecowitt protocol push\n                  ├── /api/observations/latest\n                  ├── /api/observations/range?from=\u0026to=\n                  ├── /api/observations/aggregate?from=\u0026to=\u0026bucket=\n                  ├── /api/observations/today?tz=\n                  ├── /api/observations/daily?year=\n                  ├── /api/almanac?date=\n                  └── /api/config\n\nBrowser ◀── Web (Fresh v2, :8081) ◀── Engine REST API\n```\n\nTwo Deno packages in a workspace:\n\n| Package   | Description                    |\n| --------- | ------------------------------ |\n| `engine/` | Data ingest, storage, REST API |\n| `web/`    | Fresh v2 + Vite dashboard      |\n\nBoth compile to self-contained binaries via `deno compile`.\n\n## Quick Start\n\n### Prerequisites\n\n- [Deno](https://deno.com) v2.2+ _(development only — not required for binary or package installs)_\n- A personal weather station with a network gateway. Currently, Ecowitt GW-series gateways (GW1000, GW1100, GW2000) are supported via WU push, Ecowitt push, and LAN API polling.\n\n### Setup\n\n```bash\ngit clone https://github.com/cngarrison/zephyr\ncd zephyr\ndeno install\ncp zephyr.toml.example zephyr.toml\n# Edit zephyr.toml — set station name, lat/lon, timezone, storage path, etc.\n```\n\n### Run (development)\n\n```bash\n# Engine only\ndeno task engine\n\n# Web dev server only\ndeno task web:dev\n\n# Both\ndeno task dev\n```\n\n### Configure your gateway\n\nPoint your gateway to push to the engine:\n\n| Protocol            | URL                                 |\n| ------------------- | ----------------------------------- |\n| Weather Underground | `http://\u003chost\u003e:8080/ingest/wu`      |\n| Ecowitt             | `http://\u003chost\u003e:8080/ingest/ecowitt` |\n\nFor Ecowitt gateways (GW1000 etc.): in the Ecowitt app or gateway web UI, go to _Weather Services → Customized_ and set the server IP, path, and port.\n\n### Test ingest\n\n```bash\ncurl \"http://localhost:8080/ingest/wu?tempf=72.5\u0026humidity=65\u0026baromrelin=29.92\u0026windspeedmph=5\u0026winddir=225\u0026solarradiation=450\u0026UV=3\"\ncurl http://localhost:8080/api/observations/latest\n```\n\n## Configuration\n\nZephyr uses a single TOML config file. In development, place `zephyr.toml` in the project root. In production, the default path is `/etc/zephyr/zephyr.toml`.\n\nSee [`zephyr.toml.example`](zephyr.toml.example) for all options.\n\n### Key settings\n\n```toml\n[engine]\nport = 8080\nhost = \"0.0.0.0\"\n\n[web]\nengine_url = \"http://localhost:8080\"\n\n[storage]\nprovider = \"sqlite\"   # or \"mysql\"\n\n[storage.sqlite]\npath = \"/var/lib/zephyr/zephyr.db\"\n\n[[stations]]\nid     = \"my-station\"\nname   = \"My Weather Station\"\nlat    = 51.5074\nlon    = -0.1278\naltitude  = 10\ntimezone  = \"Europe/London\"\n\n[stations.ingest.push]\nenabled = true\n```\n\nThe `PORT` and `HOSTNAME` environment variables for the web daemon are set via systemd `Environment=` in the unit file and read directly by Fresh — they are not part of `zephyr.toml`.\n\n## Storage\n\nStorage is plugin-based. The `DB_PROVIDER` setting in `[storage]` selects the adapter. Each adapter lives under `engine/src/storage/providers/` and owns its own forward-only migration runner.\n\n| Provider | Config section     | Notes                                                    |\n| -------- | ------------------ | -------------------------------------------------------- |\n| `sqlite` | `[storage.sqlite]` | Default. `path` supports NFS/network mounts.             |\n| `mysql`  | `[storage.mysql]`  | Requires `host`, `port`, `user`, `password`, `database`. |\n\nAll values are stored in SI units. Imperial→SI conversion happens in the ingest normaliser.\n\n## REST API\n\nAll endpoints are served by the engine on `:8080`. The web daemon proxies `/api/*` and `/ingest/*` requests transparently.\n\n```\nGET  /api/observations/latest\nGET  /api/observations/range?from=\u003cepoch\u003e\u0026to=\u003cepoch\u003e\nGET  /api/observations/aggregate?from=\u003cepoch\u003e\u0026to=\u003cepoch\u003e\u0026bucket=\u003chour|day\u003e\nGET  /api/observations/today?tz=\u003cIANA\u003e\nGET  /api/observations/daily?year=\u003cYYYY\u003e\nGET  /api/almanac?date=\u003cYYYY-MM-DD\u003e\nGET  /api/config\n\nGET  /ingest/wu        (WU protocol push)\nPOST /ingest/ecowitt   (Ecowitt protocol push)\n```\n\n## Development\n\n```bash\n# Type check\ndeno task check:types:all\n\n# Lint\ndeno lint\n\n# Format\ndeno fmt\n\n# Build web for production\ndeno task web:build\n\n# Compile binaries\ndeno task compile\n```\n\n## Installation (Linux server)\n\n### One-liner (tarball)\n\nInstalls the latest release, creates the `zephyr` system user, installs systemd\nunits, and prints next steps:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/cngarrison/zephyr/main/scripts/install.sh | sudo bash\n```\n\nPin to a specific version:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/cngarrison/zephyr/main/scripts/install.sh | sudo bash -s v0.1.1\n```\n\nSupports `x86_64` and `aarch64` (including Raspberry Pi 4/5 running 64-bit OS).\n\n### `.deb` package (Debian / Ubuntu)\n\nFor `.deb`-managed installs with `apt upgrade` support, see [`docs/deb-install.md`](docs/deb-install.md).\n\n### After installing\n\nCreate the config file from the installed example, then start:\n\n```bash\nsudo cp /etc/zephyr/zephyr.toml.example /etc/zephyr/zephyr.toml\nsudo nano /etc/zephyr/zephyr.toml   # set station name, lat/lon, timezone\n\nsudo systemctl enable --now zephyr.target\njournalctl -u zephyr-engine -u zephyr-web -f\n```\n\n## Documentation\n\nFull documentation is in the [`docs/`](./docs/) directory.\n\n| Document                                                             | Description                                                       |\n| -------------------------------------------------------------------- | ----------------------------------------------------------------- |\n| [docs/install.md](./docs/install.md)                                 | All install methods — one-liner, `.deb`, manual, from source      |\n| [docs/deb-install.md](./docs/deb-install.md)                         | Detailed `.deb` guide including upgrade and removal               |\n| [docs/configure.md](./docs/configure.md)                             | Full `zephyr.toml` configuration reference                        |\n| [docs/api.md](./docs/api.md)                                         | REST API reference — all endpoints, parameters, curl examples     |\n| [docs/consumers.md](./docs/consumers.md)                             | Building your own dashboard or integration against the engine API |\n| [docs/drivers.md](./docs/drivers.md)                                 | Writing a new ingest driver (push or LAN poller)                  |\n| [docs/storage-adapters.md](./docs/storage-adapters.md)               | Implementing a new storage adapter                                |\n| [docs/themes.md](./docs/themes.md)                                   | Creating a new UI theme                                           |\n| [docs/ai-assisted-development.md](./docs/ai-assisted-development.md) | AI-first contribution guide — prompt templates, common pitfalls   |\n\n---\n\n## Contributing\n\nContributions are welcome and AI-assisted contributions are first-class. The recommended path for most new features is to use a coding assistant with [AGENTS.md](./AGENTS.md) loaded as context — it contains all the project rules, file structure, and step-by-step guides an LLM needs to generate correct code.\n\nSee **[docs/ai-assisted-development.md](./docs/ai-assisted-development.md)** for ready-to-use prompt templates for common contribution types (new ingest driver, storage adapter, web route, theme).\n\nKey areas for community extension:\n\n- **Ingest drivers** — support for additional weather station hardware (`engine/src/ingest/`)\n- **Storage adapters** — implement `StorageAdapter` interface (`engine/src/storage/adapter.ts`)\n- **Cloud uploaders** — Weather Underground, CWOP, Ecowitt cloud, etc.\n- **Web themes** — alternative Tailwind/CSS themes\n- **LAN API poller parsing** — complete the device LAN API JSON → Observation mapping (`engine/src/ingest/poller.ts`)\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for full setup instructions and PR process.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcngarrison%2Fzephyr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcngarrison%2Fzephyr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcngarrison%2Fzephyr/lists"}