Monitoring Residential Boiler Performance via ModBus

Graph of Boiler Status
Comments or questions?
Stick 'em on the blog post.

The short-ish version:

This could easily be extended to other boilers with defined ModBus interfaces, for example I think at least Lochinvar and NTI boilers should be do-able; if you get a collectd config file that works, let me know and I'll add it here.

The long version:

I recently had a Triangle Tube Prestige Trimax Solo high efficiency modulating/condensing (Mod/Con) residential boiler installed in our home. (Yes, it totally needs more names, doesn't it?)

These high efficiency boilers eke out the last few percent of efficiency by condensing the flue gas, and extracting the energy that happens during that condensation. But for this to work, the return water supply needs to be at or below the dew point of natural gas, about 135 °F. To achieve this, things need to be set up and configured properly. But how would we know?

As it turns out, this boiler has a ModBus interface built in, designed for industrial control applications. But it's well documented in the Prestige Control Supplement document published by Triangle Tube. We can use this to query the boiler and retrieve information like setpoint, supply and return temp, boiler firing rate, domestic hot water storage temp, etc, as seen in the graph above.

The physical ModBus interface in the device is ModBus/RTU, on an RS-485 serial port. So we'll need an interface to that; this will do nicely (image link goes to Amazon):

It's a cheapo RS-485 adapter from Amazon, which works fine under Linux, using the ch341 driver. It has "A" and "B" terminals; these wire directly to the A and B terminals on the boiler, which can easily be found after removing the front panel. I used a twisted pair from some cat5 cable lying around. I left the Ground connection in the boiler unconnected, because the USB adapter has no GND terminal. It works fine.
NOTE: There are 120V terminals near the ModBus terminals. TURN OFF THE POWER to the boiler before poking your screwdriver around in there! Do NOT kill yourself or your boiler!

At this point, you need to plug the USB adapter into something. A Rasberry Pi will do the trick; a nice cheap low-power computer we can dedicate to the task. Plug in the power, the USB adapter, and get it on the network however you prefer. Unless your boiler room is wired w/ Cat5, I'd suggest a wireless adapter like this one from Amazon. (images below go to Amazon).
Once that's all up and running, see if things are talking. The installer setup screen allows us to set the ModBus address; it defaults to 0 (broadcast), but I think mine was set to 1 out of the box. At any rate, 1 is what I'm using, and what the next bit of code expects. Install libmodbus and libmodbus-devel, grab a little test program I wrote called tt-status.c, compile it with:
# gcc -o tt-status -lmodbus tt-status.c
and give it a shot:
root@raspberrypi:/home/pi# ./tt-status -s /dev/ttyUSB0 
Status:
 DHW Mode
 Flame Present
 DHW Pump
Supply temp:		168 °F
Return temp:		145 °F
DHW Storage temp:	102 °F
Flue temp:		132 °F
Outdoor temp:		 35 °F
Flame Ionization:	 11 μA
Firing rate:		 25 %
Boiler Setpoint:	170 °F
CH1 Maximum Setpoint:	143 °F
DHW Setpoint:		125 °F
Woo!

At this point, things are working, and we can do whatever we like with the data. My first approach to quickly get data collection up and running was to use collectd, because it already had a ModBus plugin.

However, that plugin was limited, and didn't know how to read the registers I was interested in. So I wrote two patches, one which teaches the plugin about command 0x04 ("Read Input Registers"), and another which allows it to talk directly to /dev/ttyUSB0 rather than requiring a ModBus/TCP target. (That 2nd patch is a bit less tested; I'm currently running with the mbus daemon below, and ModBus/TCP in collectd.) Update: those patches are included as of collectd v5.5, with slightly different config syntax.

Having a ModBus/TCP target is nice, because it makes it easy to collect data from remote hosts; if you wish to do this, just install mbusd with this initscript and the data will be accessible from anywhere on your network. Note, however, that things aren't happy if you try to access /dev/ttyUSB0 directly at the same time as mbusd is running on top of it.

(If you're running raspbian on your pi, here are some debs I built with these patches (NOTE: SOME REPORTS OF BROKENNESS NOT YET SORTED OUT); and my outbound link is slow, just warning you).

At this point we're almost done; install the modified collectd, with the rrdtool and modbus plugins, and add this config which has all the right definitions and transformations to read data from the boiler. Edit it as needed, specifying either your ModBus/TCP host, or the serial device name and baud rate. The config file does two basic things, first it defines each address that we wish to read, for example:

<Data "supply-temp">
   RegisterBase 768
   RegisterCmd ReadInput
   RegisterType Int16
   Type temperature
   Instance "supply"
</Data>
and tells collectd to gather that data. It then converts temperatures from 0.1C units to C units as needed, and converts all temperatures into Fahrenheit degrees. If you don't want Fahrenheit, just remove this rule at the bottom of the config:
  # Boiler temps are in degrees C
  <Rule "c_to_f">
    <Match "regex">
      Plugin "^modbus$"
        Type "^temperature$"
    </Match>
    # F = (C * 9) / 5 + 32
    <Target "scale">
      Factor 9
    </Target>
    <Target "scale">
      Factor 0.2
    </Target>
    <Target "scale">
      Offset 32
    </Target>
  </Rule>

Fire up collectd, and it should be happily storing data to an rrd file. You might want to configure a syslog or logfile plugin so you can keep an eye on what it's doing.

One thing I ran into is that if the boiler isn't firing, then the setpoint comes back as 0x8000, which is specified as an "invalid value" in the boiler docs. Having this value in the graphs makes the scale all wrong, of course, so we want to ignore it. I failed at writing a filter for the config file, so I just modified collectd's types.db like this to ignore invalid temperatures:

-temperature             value:GAUGE:U:U
+temperature             value:GAUGE:-100:300

Finally, to get pretty graphs, you'll need some collectd client; I used Visage simply because it seemed easiest to get up and running quickly. Install instructions are on the page, but essentially it's a ruby gem, so you need to install dependencies like:

    # apt-get install -y build-essential librrd-ruby ruby ruby-dev rubygems
or
    # yum install -y ruby-RRDtool ruby rubygems
then get the gem with
    # gem install visage-app
and start it with
    # visage-app start
and it should start running on http port 9292, i.e. http://raspberrypi:9292. Go to the builder link, select the host to use (the config file above calls it "rpi"), and add as many metrics as you like to the graph. Voila! (There are some oddities with timestamps on the graph which I also hacked around to fix, see this patch if that bugs you.)

There are a lot of things we could do with this; the first thing that comes to mind is collecting fine-grained data on firing rate, and using that as a gas meter. In my case, this is the only natural gas device in my house, so it would be interesting to see how accurate it is compared to the gas meter outside.

If you do anything interesting with all this, let me know!