mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-17 06:26:31 +03:00
CLI wrapper for the WeatherFlow Tempest API: current conditions, forecast, historical observations, and real-time UDP broadcasts. Demonstrates all cli-builder patterns in a working, testable project: - Non-interactive with --json, --dry-run, --quiet, --verbose - Lazy auth (--help and --dry-run work without a token) - Multi-device filtering (auto-skips HB hub, prefers ST > SKY > AIR) - Dual-output via emit() helper - Structured logging with log/warn/die - Stderr hygiene and import-time warning suppression - Idempotent operations - Global flags in any position (pre-parsed from argv) Includes the Python CLI script (scripts/tempest-cli) and full API field layout reference (references/tempest-api-field-layouts.md). Signed-off-by: Jasper <magnus@groktop.us>
129 lines
4.8 KiB
Markdown
129 lines
4.8 KiB
Markdown
# Tempest API Field Layouts
|
|
|
|
Quick reference for the obs_st (Tempest all-in-one) observation array layout.
|
|
The API returns observations as positional arrays — these index maps are
|
|
required for any CLI or script that reads raw observation data.
|
|
|
|
## obs_st (Tempest Device)
|
|
|
|
| Index | Field | Units | Notes |
|
|
|-------|-------|-------|-------|
|
|
| 0 | epoch | seconds UTC | |
|
|
| 1 | wind_lull | m/s | Minimum 3-second sample |
|
|
| 2 | wind_avg | m/s | Average over report interval |
|
|
| 3 | wind_gust | m/s | Maximum 3-second sample |
|
|
| 4 | wind_direction | degrees | 0-360 |
|
|
| 5 | wind_sample_interval | seconds | |
|
|
| 6 | station_pressure | MB | |
|
|
| 7 | air_temperature | C | |
|
|
| 8 | relative_humidity | % | |
|
|
| 9 | illuminance | lux | |
|
|
| 10 | uv | index | |
|
|
| 11 | solar_radiation | W/m² | |
|
|
| 12 | rain_accumulation | mm | Over last report interval |
|
|
| 13 | precipitation_type | 0=none 1=rain 2=hail | |
|
|
| 14 | avg_strike_distance | km | |
|
|
| 15 | strike_count | count | |
|
|
| 16 | battery | volts | |
|
|
| 17 | report_interval | minutes | |
|
|
| 18 | local_day_rain_accumulation | mm | |
|
|
| 19 | nc_rain_accumulation | mm | |
|
|
| 20 | local_day_nc_rain_accumulation | mm | |
|
|
| 21 | precip_analysis_type | enum | 0=none, 1=RainCheck display on, 2=off |
|
|
|
|
## obs_air (Air Sensor)
|
|
|
|
| Index | Field | Units |
|
|
|-------|-------|-------|
|
|
| 0 | epoch | seconds UTC |
|
|
| 1 | station_pressure | MB |
|
|
| 2 | air_temperature | C |
|
|
| 3 | relative_humidity | % |
|
|
| 4 | lightning_strike_count | count |
|
|
| 5 | lightning_avg_distance | km |
|
|
| 6 | battery | volts |
|
|
| 7 | report_interval | minutes |
|
|
|
|
## obs_sky (Sky Sensor)
|
|
|
|
| Index | Field | Units | Notes |
|
|
|-------|-------|-------|-------|
|
|
| 0 | epoch | seconds UTC | |
|
|
| 1 | illuminance | lux | |
|
|
| 2 | uv | index | |
|
|
| 3 | rain_accumulation | mm | |
|
|
| 4 | wind_lull | m/s | |
|
|
| 5 | wind_avg | m/s | |
|
|
| 6 | wind_gust | m/s | |
|
|
| 7 | wind_direction | degrees | |
|
|
| 8 | battery | volts | |
|
|
| 9 | report_interval | minutes | |
|
|
| 10 | solar_radiation | W/m² | |
|
|
| 11 | local_day_rain_accumulation | mm | |
|
|
| 12 | precipitation_type | 0=none 1=rain 2=hail | |
|
|
| 13 | wind_sample_interval | seconds | |
|
|
| 14 | nc_rain | mm | |
|
|
| 15 | local_day_nc_rain | mm | |
|
|
| 16 | precip_analysis_type | 0=none 1=RainCheck on 2=off | |
|
|
|
|
## API Response Quirks
|
|
|
|
### better_forecast nesting
|
|
The `daily` and `hourly` arrays live under a `forecast` wrapper key, NOT at the
|
|
top level of the response. If reading from the raw API:
|
|
|
|
```python
|
|
# Wrong (assumes top-level):
|
|
days = data.get("daily", []) # returns []
|
|
|
|
# Right (respects nesting):
|
|
fc = data.get("forecast", {})
|
|
days = fc.get("daily", [])
|
|
hours = fc.get("hourly", [])
|
|
```
|
|
|
|
The top-level keys of `/better_forecast` are:
|
|
- `current_conditions` — dict with air_temperature, conditions, icon, etc.
|
|
- `forecast` — dict containing `daily` (list) and `hourly` (list)
|
|
- `station` — metadata (elevation, agl, station_id)
|
|
- `units` — unit system for the response
|
|
- `status` — status_code, status_message
|
|
- `timezone`, `timezone_offset_minutes`, `latitude`, `longitude`, `location_name`
|
|
|
|
### Device types in /stations
|
|
Devices within a station have a `device_type` field. Known values:
|
|
- `HB` — Hub (cannot query observations — no `/observations/device/{id}` endpoint)
|
|
- `ST` — Tempest all-in-one (preferred sensor)
|
|
- `SKY` — Sky sensor
|
|
- `AIR` — Air sensor
|
|
|
|
Always filter out HB devices before auto-selecting a device for observation queries.
|
|
|
|
### Auth
|
|
Token is passed as query parameter: `?token=XXX`
|
|
No header-based auth for the swd.weatherflow.com REST API.
|
|
Personal access tokens generated at https://weatherflow.com (account → API Tokens).
|
|
|
|
### Units
|
|
Raw observations use metric (C, m/s, MB, mm).
|
|
The `better_forecast` endpoint returns unit-converted values based on station
|
|
preferences. The `units` key in the response documents which units are in use.
|
|
All temperature values are in **Celsius** regardless of station preference — CLI
|
|
must convert to °F if displaying imperial. Unit labels from the API are authority.
|
|
|
|
### Field type traps in better_forecast
|
|
|
|
The forecast endpoint uses epoch integers where you'd expect date strings, and
|
|
field names that differ from what common sense suggests:
|
|
|
|
| Field | Actual type | Common mistake | Fix |
|
|
|-------|-----------|---------------|-----|
|
|
| `daily[].day_start_local` | epoch int (e.g. 1778385600) | Assumed ISO string "2026-05-10T..." | `datetime.fromtimestamp(ts).strftime(...)` |
|
|
| `hourly[].local_hour` | int (e.g. 10) | Assumed ISO timestamp string | Use directly as `{h:02d}:00` |
|
|
| `hourly[].local_day` | int (e.g. 10 for the 10th) | N/A | Use alongside `local_hour` for time-of-day |
|
|
| `hourly[].local_time` | **does not exist** | Commonly assumed field | Use `local_hour` instead |
|
|
|
|
The hourly objects do NOT have a `local_time` or `time_string` field — just
|
|
`time` (epoch int), `local_day` (int), and `local_hour` (int, 0-23). Any code
|
|
looking for `local_time` will silently fall back to its default/"?" branch.
|