A self-hosted NOAA Weather Wire Service (NWWS-OI) platform in one static binary. Connects to the NWS's lowest-latency public text-product feed, parses every bulletin strictly (WMO headers, AWIPS/PIL, UGC, VTEC/HVTEC, segments, warning tags, polygons, storm motion), dedupes, archives, and serves it all back over a local HTTP API with a live Server-Sent Events stream. Rust core, Python bindings, CLI. NWWS-OI delivers warnings seconds after the WFO hits send — typically well ahead of the public CAP/REST mirrors.
README
nwws-rs
A self-hosted NOAA Weather Wire Service (NWWS-OI) platform in one static binary.
Connects to the NWS's lowest-latency public text-product feed, parses every
bulletin strictly (WMO headers, AWIPS/PIL, UGC, VTEC/HVTEC, segments, warning
tags, polygons, storm motion), dedupes, archives, and serves it all back over a
local HTTP API with a live Server-Sent Events stream. Rust core, Python
bindings, CLI.
# the entire setup, start to finish:
docker run -e NWWS_USERNAME=you -e NWWS_PASSWORD=secret \
-v nwws-archive:/archive -p 8080:8080 ghcr.io/fahrenheitresearch/nwws-rs
curl 'http://127.0.0.1:8080/v1/stream?pil=TOR' # tornado warnings, live, as SSE
curl 'http://127.0.0.1:8080/v1/warnings/active' # everything in effect right now
NWWS-OI delivers warnings seconds after the WFO hits send — typically well
ahead of the public CAP/REST mirrors. Credentials are free:
request them from the NWS here.
Why this exists
Running your own NWWS-OI consumer has traditionally meant gluing together an
XMPP library, a parser (usually pyIEM),
hand-rolled reconnect logic, deduplication, and storage. nwws-rs ships that
whole stack as one tool, and the parsing core is fast enough to chew through
years of archives:
products/sec (full parse)
relative
nwws-rs (via Python bindings)
31,265
19.0x
pyIEM 1.x (pure Python)
1,642
1x
Same bulletins, same machine, full parse path on both sides (headers, UGC,
VTEC, segments, geometry). The nwws-rs number includes the Python boundary
overhead; the pure-Rust path is faster still. Reproduce it:
python tools/bench_pyiem_speed.py (methodology in the script header).
Honest scope: nwws-rs covers NWWS text products. pyIEM also parses METAR,
SHEF, and other formats and is the right tool for those. For NWWS-OI ingest,
warning parsing, alerting, and archive work, nwws-rs is designed to be the
last tool you need.
Quick start
1. Self-hosted API server (the headline feature)
Grab a binary from Releases
(Linux, macOS, Windows) or build with cargo install nwws-rs --features serve,
then:
That one process connects to NWWS-OI, auto-reconnects forever with jittered
backoff and MUC history backfill, validates and dedupes every product, archives
them under date-partitioned directories, and serves:
Endpoint
What it returns
GET /v1/stream
live products as Server-Sent Events; filter with ?office=KLOT, ?pil=TOR, ?family=tornado
CORS is permissive, so a browser dashboard can consume the API directly.
--no-ingest serves an existing archive with zero credentials — useful for
replaying captured data or fronting a shared archive.
A tornado-warning webhook is a shell one-liner:
curl -N 'http://127.0.0.1:8080/v1/stream?pil=TOR' | while read -r line; do
case "$line" in data:*) echo "${line#data: }" | your-notifier ;; esac
done
2. Python
pip install nwws-rs
import nwws_rs
# Parse any NWS text product
msg = nwws_rs.parse_bulletin(open("tor.txt", "rb").read())
print(msg.heading, msg.awips_id, msg.family)
print(msg.segments[0].tornado_tag)
# Or consume NWWS-OI live, no server needed
client = nwws_rs.OiClient.connect("user", "password")
while True:
message = client.next_message()
print(message.wrapper.id, message.heading)
The nwws binary also covers inspection, replay, and research workflows over
files, directories, and archives:
nwws inspect parse + validate one input (WMO, NWWS-OI XML, PID201)
nwws replay stream a captured corpus through the parser
nwws summary source/transport/family counts
nwws active-at --at VTEC warnings active at a moment
nwws timeline [--at ] warning lifecycle records (issued/canceled/expired, polygons)
nwws lead-time --event-at --lat --lon point-event warning lead-time metrics
nwws oi connect [--count n] print live NWWS-OI messages
nwws oi archive [--duration] bounded live capture into an archive
nwws oi daemon supervised always-on ingest (auto-reconnect + backfill)
nwws serve [--bind addr] [--no-ingest] ingest daemon + HTTP API (build feature: serve)
nwws pid201 inspect|split|archive ... PID201 framed-stream (NOAAPORT/EMWIN-style) tooling
nwws archive import|verify|active-at|timeline canonical archive workflows
Most commands accept --format json|jsonl|tool-result for machine-readable
output. tool-result wraps reports in a wx.tool_result.v1 envelope with
artifacts, evidence, limitations, and provenance — built for AI-agent
consumers.
Design notes
Parsing model
NWWS is treated as multiple transport surfaces over one bulletin semantics:
raw WMO text, NWWS-OI XMPP stanzas, and PID201 framed streams. The rule
throughout: never trust the wrapper more than the bulletin. NWWS-OI
metadata is validated against the embedded WMO bulletin instead of being
accepted at face value.
src/python.rs — PyO3 bindings (abi3, one wheel per platform)
Archive layout
oi daemon / oi archive / serve write
archive/yyyy/mm/dd/////.{xml,json} —
raw stanza plus metadata sidecar, deduplicated by normalized bulletin content
(BLAKE3). Date partitioning keeps HTTP query cost bounded by the lookback
window (?days=), not by total archive size. The dedupe index survives
restarts, so reconnect backfill never double-archives.
Verification
140+ unit/integration/property/CLI/HTTP tests, including SSE end-to-end
differential comparison against pyIEM on overlapping raw-bulletin semantics
(tools/compare_pyiem.py, tools/compare_pyiem_corpus.py)
Python API tests against the built wheel
CI: 3-OS test matrix, clippy -D warnings, MSRV check, Docker smoke test
.\tools\verify.ps1 # fmt + clippy + tests + python suite
.\tools\verify.ps1 -Corpus # plus the pyIEM corpus comparison
Accuracy scope
Strict and heavily verified, but bounded to what is implemented and tested:
WMO/NWWS-OI/PID201 parsing, AWIPS/UGC/VTEC/HVTEC/segments/tags/geometry,
archive ingest and verification. It is not a NOAAPORT demodulator, not a
satellite receiver, and not proof against every malformed message ever emitted.