~nickbp/weatherlink-tools

Collects data from a WeatherLink Live device, sends it to MQTT and/or serves it via Prometheus

dd4bcc8 Cut 0.1.0

9 months ago

56e0d84 Cut 0.1.0

9 months ago

builds.sr.ht status

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

#Quickstart

To install the weatherlink-tools binary, you can run cargo install weatherlink-tools.

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

#Commands

weatherlink-tools combines support for either or both Prometheus and MQTT output:

  • --prom-listen <ip>:<port>: Serve weather in Prometheus format at /metrics on the specified endpoint. This is enabled by default, listening to 0.0.0.0:8080, but can be disabled using --prom-listen ''.
  • --mqtt-url mqtt://<ip>:<port>?client_id=<id>: Publish weather to a MQTT broker. A client ID may optionally be provided in the URL.

For arguments, see weatherlink-tools --help. Separately, the LOG_LEVEL envvar defaults to info and can be set to debug/trace for more logs or warn/error/off for fewer logs.

I've used these tools to collect data from a Davis Vantage Vue to send into other services. The following lists the configurations that have worked for me:

#Prometheus usage

TODO docs

#MQTT usage

I've used weatherlink-tools to stream weather data 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-tools 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-tools \
  --mqtt-url mqtt://<broker-ip>:1883?client_id=weatherlink-tools \
  --mqtt-topic-prefix weather.json/

On the consumer side, the WeatherLink MQTT data is translated 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_avg_last_1_min]]]] # average wind speed over last 2 min **(mph)**
                ignore = false
                name = windSpeed
            [[[[wind_dir_scalar_avg_last_10_min]]]] # scalar average wind direction over last 10 min (much smoother/cleaner than last) **(°degree)**
                ignore = false
                name = windDir
            [[[[wind_speed_hi_last_2_min]]]] # maximum wind speed over last 2 min **(mph)**
                ignore = false
                name = windGust
            [[[[wind_dir_at_hi_speed_last_10_min]]]] # gust wind direction over last 10 min (much smoother/cleaner than raw) **(°degree)**
                ignore = false
                name = windGustDir
            # 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.