~nickbp/weatherlink-mqtt

Collects data from a WeatherLink Live device, sends it to MQTT

356321c clippy and dep bump

a month ago

b159711 Clarify example usage a bit

2 months ago

builds.sr.ht status

Retrieves weather information from a WeatherLink Live and publishes it to an MQTT broker.

#Quickstart

To install weatherlink-mqtt, you can run cargo install weatherlink-mqtt.

Docker images for amd64 are also available with tags against the git SHA, see the Dockerfile.

#Options

weatherlink-mqtt exposes options via a mix of envvars and a TOML config file.

These are the supported envvars:

  • LOG_LEVEL: Amount of logging you'd like to have. Defaults to info, set to debug/trace for more logs or warn/error/off for fewer logs.

#Example usage

I'm personally using weatherlink-mqtt to collect data for a Davis Vantage Vue, where the collected data (a mix of HTTP polling and UDP broadcast) is collated and streamed to an offsite MQTT broker (Mosquitto). The MQTT topics are then consumed and processed by a WeeWX instance via the WeeWX-MQTTSubscribe plugin. But the data sent to MQTT is a nearly as-is copy of WeatherLink output which should be usable by any consumer.

I'm running weatherlink-mqtt in a docker container. The container is run with --network=host networking so that it can receive local UDP broadcast packets from the WeatherLink Live. I'm using these arguments pointing it to the MQTT broker:

/weatherlink-mqtt \
  --mqtt-url mqtt://<broker-ip>:1883?client_id=weatherlink-mqtt \
  --mqtt-topic-prefix weather.json/

On the consumer side, there's mapping the WeatherLink data to the names/units that WeeWX expects. The following is how I've configured WeeWX-MQTTSubscribe in my weewx.conf to do this. This includes mapping input field names to their equivalents in WeeWX, while also e.g. converting 0.01" bucket tip counts to incremental inches of rain. Depending on your hardware, you may need to change things, or this might mostly work as-is:

[MQTTSubscribeDriver]
    driver = user.MQTTSubscribe

    host = <broker-ip>
    port = 1883
    keepalive = 60
    log = true

    [[message_callback]]
        type = json
        # use / in field names to delimit json levels
        flatten_delimiter = "/"

    [[topics]]
        unit_system = US
        [[[weather.json/txid1]]]
            ignore = true
            [[[[temp]]]] # most recent valid temperature (°F)
                ignore = false
                name = outTemp
            [[[[hum]]]] # most recent valid humidity (%RH)
                ignore = false
                name = outHumidity
            [[[[dew_point]]]] # (°F)
                ignore = false
                name = dewpoint
            [[[[wet_bulb]]]] # (°F)
                ignore = false
                name = wetbulb
            [[[[heat_index]]]] # (°F)
                ignore = false
                name = heatindex
            [[[[wind_chill]]]] # (°F)
                ignore = false
                name = windchill
            [[[[thw_index]]]] # (°F)
                ignore = false
                name = thw
            [[[[wind_speed_last]]]] # most recent valid wind speed (mph)
                ignore = false
                name = windSpeed
            [[[[wind_dir_last]]]] # most recent valid wind direction (°degree)
                ignore = false
                name = windDir
            # We want to record the DIFF in bucket tips over time to weewx.
            # Sending the value as-is gets summed across EACH 2s report, leading to hugely exaggerated rain!
            # Let's go with the longest timespan - the annual value. This avoids not detecting value rollover.
            # The diff logic is smart enough to avoid reporting a big initial value as a big diff.
            # As such we ignore rain_rate_last and rain_60_min in favor of rain_24_hr, to increase the likelihood
            # of detecting a decrease when the value rolls over. The rate/1h values risk the rate staying exactly
            # the same across intervals, in which case we won't detect the diff.
            # Meanwhile we don't worry about the longer amounts like rainfall_monthly and rainfall_yearly because
            # if weewx is restarted then those will appear to have large diffs that will get recorded all at once.
            [[[[rainfall_year]]]] # total rain count since start of year (counts)
                ignore = false
                name = rain
                # enables recording diffs, see also:
                # https://github.com/bellrichm/WeeWX-MQTTSubscribe/blob/master/bin/user/MQTTSubscribe.py#L1273-L1277
                contains_total = true
                # if the value goes from 1234 back to 1, then report the 1 as a diff
                wrap_around = true
                # each count is 0.01"
                conversion_func = lambda x: x / 100.
            [[[[rain_60_min]]]] # total rain count over last 60 min (counts)
                ignore = false
                name = hourRain
                # each count is 0.01"
                conversion_func = lambda x: x / 100.
            [[[[rain_24_hr]]]] # total rain count over last 24 hours (counts)
                ignore = false
                name = rain24
                # each count is 0.01"
                conversion_func = lambda x: x / 100.
            [[[[rainfall_daily]]]] # total rain count since local midnight (counts)
                ignore = false
                name = dayRain
                # each count is 0.01"
                conversion_func = lambda x: x / 100.
            [[[[solar_rad]]]] # most recent solar radiation (W/m²)
                ignore = true # null in my data
                name = radiation # watt_per_meter_squared
            [[[[uv_index]]]] # most recent UV index (Index)
                ignore = true # null in my data
                name = UV
            [[[[trans_battery_flag]]]] # transmitter battery status flag (no unit)
                ignore = false
                name = outTempBatteryStatus

        [[[weather.json/local]]]
            ignore = true
            [[[[temp_in]]]] # most recent valid inside temp (°F)
                ignore = false
                name = inTemp
            [[[[hum_in]]]] # most recent valid inside humidity (%RH)
                ignore = false
                name = inHumidity
            [[[[dew_point_in]]]] # (°F)
                ignore = false
                name = inDewpoint
            [[[[heat_index_in]]]] # (°F)
                ignore = false
                name = inHeatindex # maybe?
            [[[[bar_sea_level]]]] # most recent bar sensor reading with elevation adjustment (inches)
                ignore = false
                name = barometer
            [[[[bar_absolute]]]] # raw bar sensor reading (inches)
                ignore = false
                name = pressure

#License

This project is licensed under the AGPLv3 (or later) and is copyright Nicholas Parker.