Introduction

Back in 2021, I put together a weather station using WeeWX and weewx-sdr. This worked pretty well, but it meant dedicated hardware for just weather and also meant that the driver needed to know about every piece of hardware that you want to use in order to map it into WeeWX.

When I moved, I wanted to take a different approach, where I have one SDR with rtl_433 and have that send messages over MQTT. I don’t love MQTT, but it works well enough as a message bus, and with a little bit of configuration, I can have both Home Assistant and WeeWX consume the data without having to build bridges from things like InfluxDB.

The listener

While I could have run this on the same host that I run home assistant, it’s located near the networking hardware, and this isn’t a great place to listen for transmissions. For that, the attic makes a lot more sense. I reused the same Raspberry Pi that I had used for the weather station, but now it publishes anything it can decode over MQTT. I’m also still using an RTL-SDR.com dongle because they are high quality.

For ease of moving stuff around as well as having to worry less about the OS software, I’m doing all of this stuff with docker this time around, and that includes rtl_433.

After plugging it in, I looked for the USB device to map it into the docker container:

$ lsusb
Bus 001 Device 004: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T
Bus 001 Device 005: ID 0424:7800 Microchip Technology, Inc. (formerly SMSC) 
Bus 001 Device 003: ID 0424:2514 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
Bus 001 Device 002: ID 0424:2514 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

On my Raspberry Pi 3B, it showed up on Bus 001 as Device 004.

I use this docker-compose.yml file to set it up:

services:
  rtl433:
    image: hertzg/rtl_433:latest
    devices:
      - "/dev/bus/usb/001/004"
    restart: unless-stopped
    command:
      - "-Mutc"
      - "-Fmqtt://<broker-hostname>:1883,retain=0,events=rtl_433[/model][/id]"

A quick docker compose up -d later and I was getting messages such as these in mosquitto:

rtl_433/Acurite-Atlas/905 {"time":"2025-01-02 16:56:29","model":"Acurite-Atlas","id":905,"channel":"C","sequence_num":0,"battery_ok":1,"message_type":39,"wind_avg_mi_h":4.0,"uv":1,"lux":10250,"strike_count":0,"strike_distance":31,"exception":0,"raw_msg":"0389e782818881009f1e"}
rtl_433/Acurite-Atlas/905 {"time":"2025-01-02 16:56:29","model":"Acurite-Atlas","id":905,"channel":"C","sequence_num":1,"battery_ok":1,"message_type":39,"wind_avg_mi_h":4.0,"uv":1,"lux":10250,"strike_count":0,"strike_distance":31,"exception":0,"raw_msg":"0789e782818881009f22"}
rtl_433/Acurite-Atlas/905 {"time":"2025-01-02 16:56:29","model":"Acurite-Atlas","id":905,"channel":"C","sequence_num":2,"battery_ok":1,"message_type":39,"wind_avg_mi_h":4.0,"uv":1,"lux":10250,"strike_count":0,"strike_distance":31,"exception":0,"raw_msg":"0b89e782818881009f26"}

Mosquitto was straightforward to set up on the host that I had running home assistant with this docker compose file:

services:
  mosquitto:
    image: eclipse-mosquitto
    container_name: mosquitto
    restart: unless-stopped
    volumes:
      - /home/<user>/docker/mosquitto/config:/mosquitto
    ports:
      - 1883:1883
      - 9001:9001

Home Assistant Aside

I found that setting up the listener to emit messages in that format works well with home assistant, since you can set up entities like this:

    - name: "Weather Station Temperature"
      state_topic: rtl_433/Acurite-Atlas/905/msg37
      unit_of_measurement: '°F'
      device_class: temperature
      value_template: '{{ value_json.temperature_F }}'
    - name: "Weather Station Humidity"
      state_topic: rtl_433/Acurite-Atlas/905/msg37
      device_class: humidity
      unit_of_measurement: '%'
      value_template: '{{ value_json.humidity }}'
    - name: "Weather Station Wind Speed Average"
      state_topic: rtl_433/Acurite-Atlas/905/msg38
      device_class: wind_speed
      unit_of_measurement: 'mph'
      value_template: '{{ value_json.wind_avg_mi_h }}'
    - name: "Weather Station Wind Direction"
      state_topic: rtl_433/Acurite-Atlas/905/msg38
      unit_of_measurement: '°'
      value_template: '{{ value_json.wind_dir_deg}}'
    - name: "Weather Station Cumulative Rain"
      state_topic: rtl_433/Acurite-Atlas/905/msg38
      device_class: precipitation
      unit_of_measurement: 'in'
      value_template: '{{ value_json.rain_in }}'
    - name: "Weather Station UV Index"
      state_topic: rtl_433/Acurite-Atlas/905/msg39
      value_template: '{{ value_json.uv }}'
    - name: "Weather Station Illuminance"
      state_topic: rtl_433/Acurite-Atlas/905/msg39
      device_class: illuminance
      unit_of_measurement: 'lx'
      value_template: '{{ value_json.lux }}'
    - name: "Weather Station Cumulative Lightning Strikes"
      state_topic: rtl_433/Acurite-Atlas/905/msg39
      value_template: '{{ value_json.strike_count }}'
      # See https://www.wxforum.net/index.php?topic=34293.0
    - name: "Weather Station Last Lightning Raw Strike Distance"
      state_topic: rtl_433/Acurite-Atlas/905/msg39
      value_template: '{{ value_json.strike_distance }}'
    - name: "Weather Station Pressure"
      device_class: atmospheric_pressure
      state_topic: rtl_433/Oregon-BTHR918/206
      unit_of_measurement: 'hPa''

Note that there are those messages split out there. For home assistant, since the Atlas weather station sends messages in three chunks, home assistant will do weird things if they aren’t broken out by message type. If this isn’t needed, such as for my pressure sensor listed, then there is no need to refer to it by message type.

An automation such as this one will break it out into message topics for home assistant to use, but we won’t use these messages with WeeWX:

- id: '1730688156106'
  alias: RTL433 MQTT message_type Demuxer
  description: Split rtl_433/# signals into msg topics, if message_type exists
  trigger:
  - platform: mqtt
    topic: rtl_433/+/+
  condition:
  - condition: template
    value_template: '{{ trigger.payload_json.message_type != null }}'
  action:
  - data:
      payload: '{{trigger.payload}}'
      topic: rtl_433/{{trigger.payload_json.model or "UnknownModel"}}/{{trigger.payload_json.id
        or "UnknownId"}}/msg{{trigger.payload_json.message_type}}
    action: mqtt.publish
  mode: single

This ends up looking something like this:

rtl_433/Acurite-Atlas/905/msg39 {"time":"2025-01-02 16:56:29","model":"Acurite-Atlas","id":905,"channel":"C","sequence_num":0,"battery_ok":1,"message_type":39,"wind_avg_mi_h":4.0,"uv":1,"lux":10250,"strike_count":0,"strike_distance":31,"exception":0,"raw_msg":"0389e782818881009f1e"}

WeeWX Setup

For this, I used the weewx-docker and WeeWX-MQTTSubscribe. I’m running this as a driver instead of just to supplement readings from another weather station, so I’m using it in that configuration.

I’m using this docker compose file for it here:

services:
  weewx:
    image: felddy/weewx:latest
    volumes:
      - type: bind
        source: ./data
        target: /data
    restart: unless-stopped

You’ll need to install paho-mqtt, like so:

docker compose run --rm --entrypoint pip weewx install paho-mqtt

And you’ll also need to install the extension, like so:

docker compose run --rm weewx \
  extension install --yes \
  https://github.com/bellrichm/WeeWX-MQTTSubscribe/archive/refs/tags/v3.0.0.zip

With that out of the way, I then configured it. Some relevant portions of the weewx.conf configuration:

[Station]

    # Description of the station location, such as your town.
    location = WeeWX station

    # Drop a pin in a location and use the URL in google maps to get this
    # Latitude in decimal degrees. Negative for southern hemisphere.
    latitude = 0.0
    # Longitude in decimal degrees. Negative for western hemisphere.
    longitude = 0.0

    # Altitude of the station, with the unit it is in. This is used only
    # if the hardware cannot supply a value.
    altitude = 0, meter    # Choose 'foot' or 'meter' for unit

    # Set to type of station hardware. There must be a corresponding stanza
    # in this file, which includes a value for the 'driver' option.
    station_type = MQTTSubscribeDriver

    # If you have a website, you may specify an URL. The URL is required if you
    # intend to register your station. The URL must include the scheme, for
    # example, "http://" or "https://"
    #station_url = https://www.example.com

    # The start of the rain year (1=January; 10=October, etc.). This is
    # downloaded from the station if the hardware supports it.
    rain_year_start = 1

    # Start of week (0=Monday, 6=Sunday)
    week_start = 6

[MQTTSubscribeDriver]
    # This section is for the MQTTSubscribe driver.

    # The driver to use.
    # Only used by the driver.
    driver = user.MQTTSubscribe

    # Controls if validation errors raise an exception (stopping WeeWX from starting) or only logged.
    # Default is false
    stop_on_validation_errors = true

    host = mqtt.example.com

    # Controls the MQTT logging.
    # Default is false.
    log = true

    [[topics]]
        # The WeeWX unit_system of the incoming data.
        unit_system = METRIC

        # Adjust this to account for the window that the multiple payloads get received
        adjust_start_time = 30

        # Barometer
        [[[rtl_433/Oregon-BTHR918/206]]]
            ignore = True
            [[[[message]]]]
                type = json

            [[[[pressure_hPa]]]]
                ignore = False
                name = pressure
                units = hPa

            [[[[temperature_C]]]]
                ignore = False
                name = inTemp
                units = degree_C

            [[[[humidity]]]]
                ignore = False
                name = inHumidity

        # Weather station
        [[[rtl_433/Acurite-Atlas/905]]]
            # Explicitly select fields
            ignore = True

            # We're doing json
            [[[[message]]]]
                type = json

            # See https://www.weewx.com/docs/latest/reference/units/?h=measurement
            [[[[temperature_F]]]]
                ignore = False
                name = outTemp
                units = degree_F

            [[[[humidity]]]]
                ignore = False
                name = outHumidity

            [[[[lux]]]]
                ignore = False
                name = illuminance

            [[[[rain_in]]]]
                ignore = False
                name = rain
                units = inch
                contains_total = true

            [[[[wind_avg_mi_h]]]]
                ignore = False
                name = windSpeed
                units = mile_per_hour

            [[[[wind_dir_deg]]]]
                ignore = False
                name = windDir

            [[[[uv]]]]
                ignore = False
                name = UV

You should validate that the data services section does not have the service version of the MQTT subscriber because then it will have weird behavior if you don’t configure it twice.

You might also find that changing the log level to DEBUG at the bottom of the file from INFO is helpful while debugging. Note that there are a couple of things going on above, but essentially you have to say that you’re ignoring all the fields and only cherry pikcing some of them.

And with that, everything seems to work.