WeatherFlow Tempest (Local UDP)
Local-network integration for the WeatherFlow Tempest weather station. The Tempest hub broadcasts JSON datagrams onto the LAN on UDP port 50222; this driver binds that port and decodes them. No token, no cloud account, no polling — if the hub and the GEM controller share a broadcast domain, the data arrives on its own.
This is the companion to the WeatherFlow Tempest cloud driver. Same hardware, opposite transport.
Which driver do I want?
| Local UDP (this page) | Cloud REST | |
|---|---|---|
| Works without internet | Yes | No |
| Needs a Personal Access Token | No | Yes |
| Observation latency | ~1 min, pushed | ~1 min, polled (plus poll interval) |
3-second wind (rapid_wind) | Yes | No |
| Lightning / rain-start events as they happen | Yes | No |
Forecast (forecast_*) | No | Yes |
| Battery, RSSI, sensor fault bits | Yes | No |
| Works when GEM is on another VLAN or off-site | No | Yes |
| Rain totals for the hour / day | Accumulated locally | From WeatherFlow |
Neither transport is complete on its own. Running both against one station is the intended full-coverage setup — see Running both drivers below, which has one rule you must follow.
What this driver is for
Read-only. There is nothing to control on a weather station; the value is
making live readings available to macros, dashboards, and the weather
automation variable — with the local path's lower latency and no dependency on
the WeatherFlow cloud being reachable.
The 3-second rapid_wind frame is the thing the cloud path cannot give you. If
you have a macro that retracts an awning, closes a louvre roof, or drops a
shade on a wind gust, the difference between a 3-second reading and a 1-minute
average is the difference between reacting to the gust and reacting to its
aftermath.
Prerequisites
| Item | Notes |
|---|---|
| Tempest commissioned in the WeatherFlow app | Wi-Fi onboarding is mobile-only. This driver does not configure the hub. |
| Layer-2 reachability | Hub and controller on the same VLAN/subnet, broadcast traffic not filtered. A routed network, a guest SSID, or AP client-isolation all block the frames. |
| UDP 50222 open inbound | On the controller's host firewall. |
There is nothing to authenticate. That is also the security caveat — see Security.
Setup
-
Confirm the hub is online in the WeatherFlow Tempest mobile app.
-
Add the device in GEM.
- Navigate to
/admin/devices→ Add Device. - Driver:
weatherflow_tempest_udp. - Leave every optional attribute at its default unless a note below applies. There is no address, port, or credential to enter for a normal install.
- Navigate to
-
Wait about a minute, then run
get_devices. The response lists every serial heard on the wire since the device connected:{"listening": true,"port": 50222,"bind_address": "0.0.0.0","devices": [{ "serial_number": "ST-00000512", "hub_sn": "HB-00013030","source_ip": "192.168.1.44", "types": ["obs_st", "rapid_wind", "device_status"],"first_seen": 1754600000000, "last_seen": 1754600183000, "bound": false }]}An empty
deviceslist means the broadcast is not reaching the controller. Go to Troubleshooting — it is a network problem, not a driver problem, and onetcpdumpsettles it. -
Create one zone per sensor.
- Subsystem: typically
weather. zone.address= the serial number from step 3, e.g.ST-00000512. Matching is case-insensitive.- This is not the cloud
station_id. A zone addressed with a station id will never match a frame.
- Subsystem: typically
-
(Optional) Set
elevation_mon the device — the sensor's height above sea level in metres. Without it,sea_level_pressure_mbcannot be computed and is skipped entirely. -
(Optional) Set
hub_serialif more than one Tempest hub is audible on this LAN — a neighbour's station, or a second building on a flat network — so only yours is accepted.
Addressing
Every frame carries the serial of the hardware that produced it, so that serial is the zone address:
| Prefix | Hardware | Frames it produces |
|---|---|---|
ST- | Tempest sensor | obs_st, rapid_wind, evt_strike, evt_precip, device_status |
AR- | Legacy AIR sensor | obs_air, evt_strike, device_status |
SK- | Legacy SKY sensor | obs_sky, rapid_wind, evt_precip, device_status |
HB- | Hub | hub_status |
A current Tempest station is one ST- sensor plus one HB- hub. Address the
ST- serial for weather data. Adding a second zone on the HB- serial is
optional and gives you hub diagnostics only (uptime, RSSI, firmware, reset
flags) — useful when you are chasing an intermittent station.
Attributes
Device attributes
| Name | Required | Default | Description |
|---|---|---|---|
listen_port | no | 50222 | Port to bind. Fixed in hub firmware; change only for a local relay or test harness. |
bind_address | no | 0.0.0.0 | Interface to listen on. Pin to one address on a multi-homed controller where only one network carries the hub. |
hub_serial | no | — | Accept only frames whose hub_sn matches. Empty accepts every hub heard. |
allowed_source_ips | no | — | Comma-separated source addresses permitted to send frames. Empty accepts any LAN source. |
units_system | no | both | metric (°C / m/s / mm), imperial (°F / mph / inches), or both. Matches the cloud driver. |
elevation_m | no | — | Sensor height above sea level, metres. Required for sea_level_pressure_mb. |
offline_timeout | no | 300000 | Silence in ms before a zone is marked disconnected. Observations arrive every ~1 min, so 5 min tolerates a few dropped frames. |
publish_rapid_wind | no | true | Write the 3-second wind reading to wind_rapid_*. Turn off to cut ~20 attribute writes per minute per station. |
Zone attributes (populated by the driver)
Attribute names match the cloud driver wherever both
transports carry the same quantity, so a widget, macro, or weather variable
reads identically no matter which driver feeds the zone.
Current observation
| Attribute | Unit | Notes |
|---|---|---|
temperature_c / temperature_f | °C / °F | Ambient air. |
humidity | % | Relative humidity. |
station_pressure_mb | mb | As measured at the sensor. |
pressure_mb / pressure_in_hg | mb / inHg | Same value as station_pressure_mb, under the cloud driver's field name. |
wind_avg_mps / wind_avg_mph | m/s / mph | Average over the report interval. |
wind_gust_mps / wind_gust_mph | m/s / mph | Maximum 3-second sample in the interval. |
wind_lull_mps / wind_lull_mph | m/s / mph | Minimum 3-second sample in the interval. |
wind_direction_deg | degrees | 0 = North, 90 = East. |
solar_radiation_wm2 | W/m² | Total irradiance. |
uv_index | index | UV index, 0–11+. |
brightness_lux | lux | Ambient brightness. |
precip_mm_min | mm | Rain in the previous minute. |
precip_type / precip_type_label | code / string | 0 none, 1 rain, 2 hail, 3 rain + hail. |
raining | bool | True when precip_type > 0 or a rain-start event just landed. |
lightning_count | count | Strikes in the report interval, as the hub counts them. |
lightning_avg_distance_km | km | Mean distance over the report interval — see naming. |
battery_voltage | V | Sensor battery. |
report_interval_min | min | The sensor's own reporting cadence. |
wind_sample_interval_s | s | Wind sampling cadence. |
observation_timestamp | unix s | Useful for staleness checks. |
Rapid wind (every ~3 seconds)
| Attribute | Unit | Notes |
|---|---|---|
wind_rapid_mps / wind_rapid_mph | m/s / mph | Instantaneous wind speed. |
wind_rapid_direction_deg | degrees | Instantaneous direction. |
wind_rapid_timestamp | unix s | When the sample was taken. |
Suppressed entirely when publish_rapid_wind is false.
Events
| Attribute | Unit | Notes |
|---|---|---|
lightning_last_distance_km | km | Distance to the most recent strike, from an evt_strike frame. |
lightning_last_energy | — | Raw energy figure for that strike. |
lightning_last_timestamp | unix s | When it struck. |
precip_started_timestamp | unix s | When rain was last detected starting. |
Accumulated by this driver
| Attribute | Unit | Notes |
|---|---|---|
precip_1hr_mm / precip_1hr_in | mm / in | Rolling one-hour total. |
precip_today_mm / precip_today_in | mm / in | Local-day total, reset at local midnight. |
precip_accum_date | string | YYYY-MM-DD day key the daily total belongs to. Written so a restart can tell today's total from yesterday's leftovers. |
lightning_count_1hr / lightning_count_3hr | count | Rolling strike counts from evt_strike frames. |
See Accumulated values for what survives a restart.
Derived by this driver
The broadcast carries raw sensor values only. These are computed locally using the standard published formulas — NWS heat index and wind chill, Magnus dew point, Stull wet bulb, ideal-gas air density, hypsometric sea-level reduction. Expect small differences from the cloud driver's values, which come from WeatherFlow's own implementation.
| Attribute | Unit | Notes |
|---|---|---|
dew_point_c / dew_point_f | °C / °F | Magnus-Tetens. |
feels_like_c / feels_like_f | °C / °F | Heat index when hot, wind chill when cold and windy, air temperature in between. |
heat_index_c | °C | Reports air temperature below ~27 °C, where the index is undefined. |
wind_chill_c | °C | Reports air temperature above ~10 °C or in calm air, where the index is undefined. |
wet_bulb_c | °C | Stull (2011) approximation, ~0.3 °C. |
air_density_kgm3 | kg/m³ | Humid-air density by partial pressures. |
sea_level_pressure_mb | mb | Requires elevation_m. Skipped entirely when unset. |
Diagnostic
| Attribute | Notes |
|---|---|
signal_strength | Sensor RSSI in dBm (hub RSSI on an HB- zone). |
hub_signal_strength | Hub's view of this sensor, dBm. |
sensor_status / sensor_status_text | Fault bitfield and a decoded string (ok, or wind failed, rh failed). |
uptime_seconds | Since the unit last reset. |
firmware_version | Firmware revision reported in the frame. |
hub_reset_flags | HB- zones. BOR brownout, PIN pin, POR power, SFT software, WDG watchdog, WWD window watchdog, LPW low power, HRDFLT hard fault. |
hub_radio_status | HB- zones. 0 off, 1 on, 3 active, 7 BLE connected. |
serial_number / hub_serial_number | Echoed from the frame. |
Commands
| Name | Args | Description |
|---|---|---|
get_devices | — | Every serial heard since connect, with hub, frame types, source IP and last-seen. This is how you find the zone address. |
get_status / get_observation | address | Decoded state for one serial — last frame of each type, accumulator state, and which attributes are derived rather than measured. |
refresh | address (optional) | Re-publish the last decoded observation onto the zone attributes, for one serial or every bound zone. |
There is no get_forecast and no poll command. The broadcast is push-only —
there is nothing to ask the hub for, and refresh replays what was last heard
rather than fetching anything.
Running both drivers
Both drivers may point at the same physical station, and the usual reason to do it is that neither transport is complete: only the cloud has forecasts, only the broadcast has 3-second wind and hardware diagnostics.
The rule: give each driver its own zone. The addresses differ anyway —
12345 (cloud station id) versus ST-00000512 (hardware serial) — so this
happens naturally if you follow each driver's setup steps. Pointing both at one
zone makes the slower writer overwrite the faster one on every shared field,
and produces attribute history that alternates between two sources.
A common arrangement:
- Zone
weather_outsideon the UDP driver — everything live, drives the gust macros and the dashboard. - Zone
weather_forecaston the cloud driver — theforecast_*attributes only, read by the "will it rain tomorrow" logic.
Accumulated values
The broadcast reports rain only as millimetres in the previous minute
(obs_sky index 11, the daily total, is documented as null over UDP). Hourly
and daily totals are therefore summed by this driver rather than read off the
wire, which has consequences worth knowing:
precip_today_mmsurvives a restart when the storedprecip_accum_datestill matches today. The driver re-seeds from the zone attribute on the first frame after connect. If the stored day is older, it correctly restarts at zero rather than adopting yesterday's total.precip_1hr_mmrestarts at zero after a restart and reaches full accuracy an hour later. There is nothing to re-seed it from.- Repeated timestamps are not double-counted. The hub re-sends on retry; a frame whose timestamp has not advanced is reported from existing state rather than added to it.
- Rolling lightning counts cover strikes GEM heard.
lightning_count_1hrandlightning_count_3hrare built fromevt_strikeframes, so a strike that happened while GEM was down is not in them.lightning_count— the hub's own count for the report interval — is unaffected.
A note on lightning naming
lightning_avg_distance_km (from an observation) is the mean distance over
the report interval. The cloud driver's lightning_last_distance_km is the
distance to the last strike. They are different quantities, so this driver uses
different names and publishes lightning_last_distance_km only from an
evt_strike frame, where it really is the last strike.
Security
A broadcast listener is unauthenticated by construction. Any host on the LAN can send a well-formed frame to UDP 50222 and this driver will believe it, the same as it believes the hub. If weather readings drive physical actuation — retracting an awning, closing a roof — treat that as part of the threat model:
- Set
allowed_source_ipsto the hub's address. Give the hub a DHCP reservation first so it does not move. - Set
hub_serialso frames from any other hub are rejected even from an allowed address. - Both filters are off by default, because on a normal trusted home LAN they are friction for no gain. Turn them on where the LAN is not trusted.
Frames larger than 8 KB, malformed JSON, and frames missing serial_number or
type are dropped before parsing goes anywhere. Rejected sources are logged at
most once per minute per reason, so a persistent bad broadcaster cannot flood
the log.
Known limitations
- Broadcast only. If GEM is on another VLAN, behind a router, or on an SSID
with client isolation, nothing arrives and there is no error to see —
get_devicessimply stays empty. Use the cloud driver on those networks. - No forecast. Better Forecast is a cloud product with no broadcast equivalent.
- Derived fields are approximations. Close to WeatherFlow's, not identical.
sea_level_pressure_mbneedselevation_mand is skipped without it.- No
pressure_trend. The cloud computes it over a longer window than this driver keeps. - Rapid wind is a lot of writes. ~20 attribute writes per minute per
station. Enable history on
wind_rapid_*only if you genuinely want 3-second trending — it is a very large number of rows.
Troubleshooting
| Symptom | What to check |
|---|---|
get_devices returns an empty list, device never connects | Frames are not reaching the host. Run tcpdump -i any udp port 50222 on the controller: no packets means the network (VLAN, client isolation, host firewall); packets means the driver. Confirm hub and controller are on the same subnet. |
| Device connects but a zone stays disconnected | The zone address does not match a serial on the wire. Run get_devices and copy the serial exactly — a zone addressed with the cloud station_id will never match. |
sea_level_pressure_mb never appears | elevation_m is unset on the device. |
precip_today_mm reads zero after a restart during rain | Expected when the stored accumulation was for a previous day or the attribute was cleared. It resumes counting from the next rain frame. |
bind EADDRINUSE in the log | Another process holds 50222 without SO_REUSEADDR. The driver retries every 5 s and recovers once that listener releases the port. |
| Attribute history is enormous | Rapid wind writes every ~3 s. Disable history on wind_rapid_* in /admin/attributes, or set publish_rapid_wind to false. |
| Readings look wrong or impossible | Something else on the LAN may be broadcasting frames. Set allowed_source_ips to the hub's IP and hub_serial to its serial. |
See also
- WeatherFlow Tempest — the cloud REST driver for the same hardware. The one to use for forecasts, or when GEM is not on the hub's LAN.
- Automation variables — the
weathervariable can be pointed at a Tempest zone instead of an external API.