FauxFaux / neohub-mqtt

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

neohub-mqtt

This application will poll a heatmiser neostat smart heating hub, and output temperature and heat information to an MQTT broker of your choice.

From there, you can ingest the data to e.g. Home Assistant or InfluxDB, and store and view graphs Your Way:

an example graph in Grafana

Installation

cargo build --release
sudo cp -ar target/release/neohub-mqtt /usr/local/bin/

You must set some environment variables:

  • where to read from, see the neohub crate docs, and
  • where to write to, currently only a host IP is supported.

e.g.

MQTT_HOST=192.168.4.20
NEOHUB_URL=wss://192.168.13.37:4243
NEOHUB_TOKEN=69696969-6969-4969-6969-696969696969
RUST_LOG=info

It should, on startup, log (within a second or two):

neohub_mqtt > found hub with id "01:90:60:C0:0C:C0"
neohub_mqtt > mqtt broker acknowledged a publication (we're all good)

...and nothing else, at the default log level.

Operating

  • If there is an error reading from the hub, the application will exit. It expects to be restarted by the supervisor, e.g. systemd.

  • If there is an error writing to the broker, the application will buffer events in memory for "minutes". That is, short outages of the broker will be tolerated, so long as there are no other errors at the same time.

  • During an exit, such as is initiated by a hub error, the application will make some effort to flush events to the broker.

Example stack

Example queries

The temperature in every zone:

from(bucket: "house")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "neohub")
  |> filter(fn: (r) => r["_field"] == "temp")
  |> keep(columns:["_field", "_value", "zone_name", "_measurement", "_time"])
  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
  |> yield(name: "mean")

The set temp in every zone except "Hot Water", which doesn't have a (sane) set temp:

from(bucket: "house")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "neohub")
  |> filter(fn: (r) => r["_field"] == "temp_set")
  |> filter(fn: (r) => r["zone_name"] != "Hot Water")
  |> keep(columns:["_field", "_value", "zone_name", "_measurement", "_time"])
  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
  |> yield(name: "mean")

Whether the zone is calling for heat:

from(bucket: "house")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "neohub")
  |> filter(fn: (r) => r["_field"] == "heat_on")
  |> filter(fn: (r) => r["zone_name"] != "Hot Water")
  |> keep(columns:["_field", "_value", "zone_name", "_measurement", "_time"])
  |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)
  |> yield(name: "last")

About


Languages

Language:Rust 100.0%