A weather station on APRS is one of the more genuinely useful things on the network. Position beacons tell you where somebody’s car is; a weather beacon contributes data that feeds severe-weather nowcasting, and through CWOP it reaches the National Weather Service’s assimilation systems.
The packet format is compact, positional, and contains one trap that catches nearly everyone writing a parser.
The weather data block
Weather values are encoded as a letter followed by a fixed number of digits, concatenated with no separators:
c220s004g005t077r000p000P000h50b10138
That reads: wind from 220°, sustained 4 mph, gusting 5 mph, 77 °F, no rain, 50% humidity, 1013.8 hPa.
The fields:
| Field | Width | Meaning | Units |
|---|---|---|---|
c |
3 | Wind direction | Degrees, 001–360 (000 means north or unknown) |
s |
3 | Wind speed, sustained | mph, one-minute average |
g |
3 | Wind gust | mph, peak in the last 5 minutes |
t |
3 | Temperature | °F, may be negative as t-05 |
r |
3 | Rainfall, last hour | Hundredths of an inch |
p |
3 | Rainfall, last 24 hours | Hundredths of an inch |
P |
3 | Rainfall since local midnight | Hundredths of an inch |
h |
2 | Humidity | Percent; h00 means 100% |
b |
5 | Barometric pressure, sea level | Tenths of a hPa/millibar |
L |
3 | Luminosity, 0–999 | W/m² |
l |
3 | Luminosity, 1000–1999 | W/m², subtract 1000 from the encoded value |
s |
3 | Snowfall, last 24 hours | Inches — see the warning below |
# |
3 | Raw rain counter | Tips |
F |
3 | Water level / flood stage | Feet |
V |
3 | Battery voltage | Tenths of a volt |
Unknown values are sent as dots or spaces in place of the digits: c...s...g...t077 is a station that knows the temperature and nothing else about the wind.
s means two different things depending on where it appears.
In the mandatory leading group c…s…g…t… it is wind speed in mph. Anywhere after that group, s is snowfall in inches over 24 hours.
This is not a documentation ambiguity you can ignore — a parser that scans for s anywhere in the block will happily report a 4-inch snowfall on a calm summer afternoon. Parse the c/s/g/t prefix positionally, as a unit, and only then scan the remainder for the optional letters.
The h00 = 100% rule is the second-most-common parser bug, for the same reason: it looks like a missing value and is not.
The four fields c, s, g, t are conventionally required and must appear first, in that order. Everything after them is optional and order is not strictly enforced, though most software emits them in the order in the table.
A weather report may end with a short identifier for the hardware and software producing it, such as wRSW or .DsVP. It is free-form and purely informational.
Positionless reports
Data type identifier _, followed by an 8-digit MMDDHHMM timestamp in UTC, then the weather block:
N0CALL>APRS,TCPIP*:_09191830c220s004g005t077r000p000P000h50b10138
This form carries no coordinates. It is used by stations whose position is already known to the network from a separate position beacon, and it is the most efficient option for a fixed station — there is no reason to retransmit coordinates that have not changed.
Positioned weather reports
The alternative combines a position report with weather data. The symbol code must be _, the weather station symbol, and the weather block follows immediately after the symbol:
N0CALL>APRS,TCPIP*:@191830z3554.12N/08646.50W_220/004g005t077r000p000P000h50b10138
Note what changed in the middle: where a normal position report would have course and speed as 088/043, a positioned weather report reuses those same slots for wind direction and wind speed. 220/004 is wind from 220° at 4 mph. The c and s letters are omitted because the positional slots already carry that data; the block resumes at g.
That reuse is elegant and is another place parsers go wrong — the same three-digit-slash-three-digit pattern means course/speed on a car and wind direction/speed on a weather station, and the only thing distinguishing them is the symbol code.
Data type identifiers !, =, / and @ all work; @ with a timestamp is the usual choice.
Getting a modern weather station onto APRS
Most current consumer weather stations — Tempest, Ecowitt, Ambient, Davis with an IP logger — have no idea APRS exists. They push to a vendor cloud over HTTPS. Bridging them takes a piece of software in the middle that reads the station and emits APRS packets.
The options, in rough order of effort
WeeWX is the usual answer. It is a mature Python weather station daemon with drivers for essentially everything and an APRS/CWOP extension. It polls or listens to the station, maintains the derived values APRS wants (24-hour rain totals, sea-level pressure), and uploads on a schedule. If you want this to work and keep working, this is the path.
A small script against the vendor API. For a station with a decent local or cloud API, a scheduled script can fetch current conditions, format the packet, and submit it. Straightforward to write; the work is not the packet formatting but the bookkeeping — rain accumulators and pressure reduction are stateful and easy to get subtly wrong.
A vendor or third-party bridge. Some ecosystems have community bridges that publish to CWOP directly. Check before building.
Local API versus cloud API
Worth knowing: several modern stations, Tempest included, broadcast observations over UDP on the local network in addition to publishing to the cloud. Listening locally is lower latency, has no rate limit, no API key, and keeps working when the internet is down — which for a weather station feeding emergency communications is the entire point.
If you are building a bridge and the station supports a local broadcast, prefer it over the cloud API.
Units are the tedious part
Your station almost certainly reports metric or a user-chosen mix. APRS is rigidly imperial and rigidly scaled:
wind speed m/s → mph × 2.23694
temperature °C → °F × 9/5 + 32
rainfall mm → 0.01 in ÷ 0.254
pressure hPa → tenths hPa × 10
Pressure is the one that causes real trouble. APRS b wants sea-level-reduced pressure, not station pressure. If your station reports both, use the sea-level figure; if it reports only station pressure, you must reduce it using your elevation, and getting this wrong puts an obviously bad barometer reading into the network. A station reporting 985 hPa at sea level on a clear day is someone who skipped this step.
CWOP
The Citizen Weather Observer Program takes amateur weather reports, quality-controls them, and feeds them into NOAA’s MADIS system, where they are used in model assimilation and by forecasters.
It rides on the APRS-IS protocol, so if you can emit an APRS weather packet you can join. The differences from normal APRS:
- Connect to
cwop.aprs.neton port 14580 rather than a general APRS-IS rotate address. - Licensed amateurs may use their call sign. Everyone else registers for a CW#### identifier, which is free.
- Send positioned reports including your coordinates, since CWOP needs to know where the observation came from.
- Report every 5 to 10 minutes. More often is not more useful and is discouraged.
Accuracy matters more here than on the RF side, because the data is actually used. Two things dominate quality:
Siting. A thermometer in direct sun or above a driveway reads high by several degrees. An anemometer under the eaves reads low and from the wrong direction. The formal guidance wants the temperature sensor shaded and ventilated at about 1.5 m over grass, and the anemometer at 10 m clear of obstructions by ten times their height. Almost nobody achieves the anemometer standard in a suburb — but knowing how far off you are is itself useful, and worth noting in your station’s comment field.
Elevation and pressure reduction. Covered above. Get the elevation right and let the software do the reduction.
If a sensor is broken, stop sending that field rather than sending a stuck value. A thermometer frozen at 77 °F for three weeks is worse than no thermometer, because quality control may take a while to catch it and a forecaster may not. Omitting a field is fully supported by the format — that is what the dots are for.
Reading other stations’ weather
If you want to consume weather from APRS rather than contribute it, the APRS-IS filter language makes it cheap. Connect to port 14580 with a receive-only passcode of -1 and a filter like:
#filter r/36.16/-86.78/100 t/w
That gives you every weather report within 100 km and nothing else — a few packets a minute instead of thousands per second. From there it is the parsing described above.
Next: APRS-IS: Servers, Filters, and Gateways covers the network this rides on, and Decoding APRS Position Reports covers the position formats in the same detail.