lowerpower/wu-upload

Acurite 433 MHz sensor to Weather Underground via RTL-SDR

2

stars

8

commits

Shell

primary language

Aug 25, 2026

updated

433mhz
acurite
bash
dew-point
heat-index
home-automation
mrtg
personal-weather-station
pws
rtl433
rtl-sdr
weather-station
weather-underground
weatherunderground
wunderground

README

wu-upload — Acurite 433 MHz → Weather Underground

Reads a temperature/humidity packet from an Acurite 433 MHz sensor with an RTL-SDR dongle, validates it, and uploads it to a Weather Underground personal weather station.

Also writes a small plain-text file that a local grapher (MRTG, Munin, a cron script — anything that can cat a file) can consume, so one RF read serves both the public upload and local graphing without a second process fighting for the SDR.

Acurite sensor
    -> RTL-SDR / rtl_433
    -> read_acurite.sh
       -> /tmp/lake_temp.txt   (local graphing)
       -> JSON -> wu-upload.sh -> Weather Underground

read_acurite.sh is the only script here that starts rtl_433. Keep it that way: two processes on one dongle will both fail.

Requirements

  • An RTL-SDR dongle
  • rtl_433 (tested against 23.11)
  • jq, stdbuf (coreutils), curl, flock (util-linux), awk
  • A Weather Underground PWS station ID and upload key

Files

FilePurpose
read_acurite.shReads one Acurite RF packet; prints it and refreshes the local data file.
wu-upload.shValidates a reading and uploads it to Weather Underground.
process_data.shNo-op compatibility shim for an older cron entry. See below.
secrets.env.exampleTemplate for the upload key file.
crontab.exampleSuggested schedule.

Setup

  1. Install the scripts. Install the reader first — the uploader looks for it at /usr/local/bin/read_acurite.sh:

    sudo install -m 755 read_acurite.sh /usr/local/bin/read_acurite.sh
    sudo install -m 755 wu-upload.sh    /usr/local/bin/wu-upload.sh
    
  2. Create the secrets file, named after your station ID:

    sudo mkdir -p /etc/pws
    sudo cp secrets.env.example /etc/pws/secrets.env
    sudo chmod 600 /etc/pws/secrets.env
    sudo editor /etc/pws/secrets.env
    

    wu-upload.sh refuses to run unless this file is mode 600 or 400.

  3. Set your station ID. Every setting has an environment override, so no edit to the script is needed:

    STATION_ID=KXXYYYY7 /usr/local/bin/wu-upload.sh
    

    STATION_ID defaults to the author's own (KCACLEAR4) — override it as above, or edit the defaults at the top of the script. If your reader lives somewhere other than /usr/local/bin/read_acurite.sh, set READER too.

  4. Install a schedule — see crontab.example.

read_acurite.sh

Listens on 433.92 MHz and returns as soon as a matching temperature/humidity packet arrives, rather than burning the whole wait window. Defaults to a 90-second window and Acurite sensor ID 460.

# Human-readable
./read_acurite.sh

# JSON, 30-second window
./read_acurite.sh --json 30

Overrides:

VariableDefaultMeaning
ACURITE_ID460Sensor ID to accept. Empty accepts any Acurite sensor.
ACURITE_WAIT_SECONDS90Read window, also settable as the positional argument.
RTL_FREQ433.92MCentre frequency. Set explicitly — the R820T reports "PLL not locked" when left to choose.
LAKE_TEMP_FILE/tmp/lake_temp.txtLocal data file to refresh.
RTL_433_BINautodetectedPath to rtl_433.

Acurite sensor IDs usually change when the battery is replaced. If readings stop, re-run with ACURITE_ID= to discover the new one:

ACURITE_ID= ./read_acurite.sh --json 60

Note that an empty ID will also pick up a neighbour's sensor if one is in range.

The local data file

Every successful read atomically replaces $LAKE_TEMP_FILE (default /tmp/lake_temp.txt). A failed read leaves the last good file in place. The file is mode 644 and holds four lines:

temperature in Fahrenheit
relative humidity percentage
system uptime
hostname

For example:

76.3
49
 10:37:12 up 2 days, 12:14, 2 users, load average: 0.22, 0.17, 0.12
lakeserver

That four-line shape is what MRTG expects from an external script (value 1, value 2, uptime, hostname), so it can be consumed directly by a target such as:

Target[lake]: `cat /tmp/lake_temp.txt`
Options[lake]: nopercent,gauge

Any tool that reads the first two lines will do just as well.

wu-upload.sh

  • Takes a non-blocking lock so overlapping runs do not fight over the SDR.
  • Calls read_acurite.sh --json with a 60-second window.
  • Rejects temperatures outside -40..140 °F and humidity outside 0..100%, rather than publishing obvious sensor faults to a permanent public archive.
  • Computes dew point (Magnus-Tetens) and heat index (NWS Rothfusz regression).
  • Uploads to Weather Underground.
  • Counts consecutive RF misses and escalates from WARN to ERROR after six. A single miss is normal — RF is lossy. Sustained silence is not.
  • Logs to $LOGFILE, scrubbing the upload key from any curl output.

Weather Underground returns HTTP 200 for every outcome, so the script checks the response body: success, or INVALIDPASSWORDID meaning the key was rejected.

Environment overrides: STATION_ID, SECRETS, READER, READ_WINDOW, LOGFILE, LOCKFILE, MISSFILE, SOFTWARE, ENDPOINT.

The upload key is read from $SECRETS and is looked up by station, so one secrets file can serve several stations:

WU_UPLOAD_KEY_KCACLEAR4=...
WU_UPLOAD_KEY_KCAOTHER9=...

process_data.sh

A deliberate no-op. An older setup ran a lake-temperature reader from /etc/crontab every five minutes, which launched its own rtl_433 and could compete with the upload job for the SDR. The scheduled upload now obtains the packet and read_acurite.sh refreshes both consumers from that single reading.

The shim exists so a stale /etc/crontab entry does nothing instead of failing. If you have no such entry, you do not need this file.

Troubleshooting

Check the latest values and when they were written:

cat /tmp/lake_temp.txt
stat /tmp/lake_temp.txt

Check uploader status and the current miss streak:

sudo tail -n 50 /var/log/pws/wu-upload.log
cat /run/pws/consecutive-misses
SymptomCause
SDR unavailableAnother process claimed the USB device. Confirm no legacy reader is scheduled or running.
No matching Acurite reportCheck the battery, widen the read window, or use ACURITE_ID= to find a changed sensor ID.
cannot read /etc/pws/secrets.envMissing file, or not readable by the user cron runs as.
is mode NNN, want 600sudo chmod 600 /etc/pws/secrets.env
credentials rejectedWrong upload key for that station ID.
rtl_433 was not foundInstall it, or set RTL_433_BIN.

License

MIT — see LICENSE.

Contributors

lowerpower

8 commits

lowerpower/wu-upload

Acurite 433 MHz sensor to Weather Underground via RTL-SDR

2

stars

8

commits

Shell

primary language

Aug 25, 2026

updated

433mhz
acurite
bash
dew-point
heat-index
home-automation
mrtg
personal-weather-station
pws
rtl433
rtl-sdr
weather-station
weather-underground
weatherunderground
wunderground

README

wu-upload — Acurite 433 MHz → Weather Underground

Reads a temperature/humidity packet from an Acurite 433 MHz sensor with an RTL-SDR dongle, validates it, and uploads it to a Weather Underground personal weather station.

Also writes a small plain-text file that a local grapher (MRTG, Munin, a cron script — anything that can cat a file) can consume, so one RF read serves both the public upload and local graphing without a second process fighting for the SDR.

Acurite sensor
    -> RTL-SDR / rtl_433
    -> read_acurite.sh
       -> /tmp/lake_temp.txt   (local graphing)
       -> JSON -> wu-upload.sh -> Weather Underground

read_acurite.sh is the only script here that starts rtl_433. Keep it that way: two processes on one dongle will both fail.

Requirements

  • An RTL-SDR dongle
  • rtl_433 (tested against 23.11)
  • jq, stdbuf (coreutils), curl, flock (util-linux), awk
  • A Weather Underground PWS station ID and upload key

Files

FilePurpose
read_acurite.shReads one Acurite RF packet; prints it and refreshes the local data file.
wu-upload.shValidates a reading and uploads it to Weather Underground.
process_data.shNo-op compatibility shim for an older cron entry. See below.
secrets.env.exampleTemplate for the upload key file.
crontab.exampleSuggested schedule.

Setup

  1. Install the scripts. Install the reader first — the uploader looks for it at /usr/local/bin/read_acurite.sh:

    sudo install -m 755 read_acurite.sh /usr/local/bin/read_acurite.sh
    sudo install -m 755 wu-upload.sh    /usr/local/bin/wu-upload.sh
    
  2. Create the secrets file, named after your station ID:

    sudo mkdir -p /etc/pws
    sudo cp secrets.env.example /etc/pws/secrets.env
    sudo chmod 600 /etc/pws/secrets.env
    sudo editor /etc/pws/secrets.env
    

    wu-upload.sh refuses to run unless this file is mode 600 or 400.

  3. Set your station ID. Every setting has an environment override, so no edit to the script is needed:

    STATION_ID=KXXYYYY7 /usr/local/bin/wu-upload.sh
    

    STATION_ID defaults to the author's own (KCACLEAR4) — override it as above, or edit the defaults at the top of the script. If your reader lives somewhere other than /usr/local/bin/read_acurite.sh, set READER too.

  4. Install a schedule — see crontab.example.

read_acurite.sh

Listens on 433.92 MHz and returns as soon as a matching temperature/humidity packet arrives, rather than burning the whole wait window. Defaults to a 90-second window and Acurite sensor ID 460.

# Human-readable
./read_acurite.sh

# JSON, 30-second window
./read_acurite.sh --json 30

Overrides:

VariableDefaultMeaning
ACURITE_ID460Sensor ID to accept. Empty accepts any Acurite sensor.
ACURITE_WAIT_SECONDS90Read window, also settable as the positional argument.
RTL_FREQ433.92MCentre frequency. Set explicitly — the R820T reports "PLL not locked" when left to choose.
LAKE_TEMP_FILE/tmp/lake_temp.txtLocal data file to refresh.
RTL_433_BINautodetectedPath to rtl_433.

Acurite sensor IDs usually change when the battery is replaced. If readings stop, re-run with ACURITE_ID= to discover the new one:

ACURITE_ID= ./read_acurite.sh --json 60

Note that an empty ID will also pick up a neighbour's sensor if one is in range.

The local data file

Every successful read atomically replaces $LAKE_TEMP_FILE (default /tmp/lake_temp.txt). A failed read leaves the last good file in place. The file is mode 644 and holds four lines:

temperature in Fahrenheit
relative humidity percentage
system uptime
hostname

For example:

76.3
49
 10:37:12 up 2 days, 12:14, 2 users, load average: 0.22, 0.17, 0.12
lakeserver

That four-line shape is what MRTG expects from an external script (value 1, value 2, uptime, hostname), so it can be consumed directly by a target such as:

Target[lake]: `cat /tmp/lake_temp.txt`
Options[lake]: nopercent,gauge

Any tool that reads the first two lines will do just as well.

wu-upload.sh

  • Takes a non-blocking lock so overlapping runs do not fight over the SDR.
  • Calls read_acurite.sh --json with a 60-second window.
  • Rejects temperatures outside -40..140 °F and humidity outside 0..100%, rather than publishing obvious sensor faults to a permanent public archive.
  • Computes dew point (Magnus-Tetens) and heat index (NWS Rothfusz regression).
  • Uploads to Weather Underground.
  • Counts consecutive RF misses and escalates from WARN to ERROR after six. A single miss is normal — RF is lossy. Sustained silence is not.
  • Logs to $LOGFILE, scrubbing the upload key from any curl output.

Weather Underground returns HTTP 200 for every outcome, so the script checks the response body: success, or INVALIDPASSWORDID meaning the key was rejected.

Environment overrides: STATION_ID, SECRETS, READER, READ_WINDOW, LOGFILE, LOCKFILE, MISSFILE, SOFTWARE, ENDPOINT.

The upload key is read from $SECRETS and is looked up by station, so one secrets file can serve several stations:

WU_UPLOAD_KEY_KCACLEAR4=...
WU_UPLOAD_KEY_KCAOTHER9=...

process_data.sh

A deliberate no-op. An older setup ran a lake-temperature reader from /etc/crontab every five minutes, which launched its own rtl_433 and could compete with the upload job for the SDR. The scheduled upload now obtains the packet and read_acurite.sh refreshes both consumers from that single reading.

The shim exists so a stale /etc/crontab entry does nothing instead of failing. If you have no such entry, you do not need this file.

Troubleshooting

Check the latest values and when they were written:

cat /tmp/lake_temp.txt
stat /tmp/lake_temp.txt

Check uploader status and the current miss streak:

sudo tail -n 50 /var/log/pws/wu-upload.log
cat /run/pws/consecutive-misses
SymptomCause
SDR unavailableAnother process claimed the USB device. Confirm no legacy reader is scheduled or running.
No matching Acurite reportCheck the battery, widen the read window, or use ACURITE_ID= to find a changed sensor ID.
cannot read /etc/pws/secrets.envMissing file, or not readable by the user cron runs as.
is mode NNN, want 600sudo chmod 600 /etc/pws/secrets.env
credentials rejectedWrong upload key for that station ID.
rtl_433 was not foundInstall it, or set RTL_433_BIN.

License

MIT — see LICENSE.

Contributors

lowerpower

8 commits

Languages

Shell

100.0%