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>
4.8 KiB
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:
# 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 containingdaily(list) andhourly(list)station— metadata (elevation, agl, station_id)units— unit system for the responsestatus— status_code, status_messagetimezone,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 sensorAIR— 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.