Residential boiler monitoring via ModBus

Graph of boiler operation, click to embiggen

We recently upgraded our boiler to a high-efficiency modulating/condensing Triangle Tube Prestige Trimax Solo, and in perusing the manual, I found that the boiler has a ModBus interface .  Woohoo, project!  The final result is live charting like you see above; for more details, read on!

Per the boiler docs, the ModBus interface is Modbus/RTU using RS-485 for the physical layer.  First thing, obviously, is to find an RS-485 adapter.  Fortunately, I found one super cheap (under $10) at Amazon, a “Kedsum USB to RS485 Converter” [amzn] and despite the cheap price and appearance, it seems to work just fine:

Cheapo USB Adapter at Amazon

The adapter only has A and B connections, and no GND, but it works fine.  I used a twisted pair from a CAT5 cable to connect to the A and B terminals on the boiler, plugged it into a spare Raspberry Pi, and was ready to test this thing.

Boiler ModBus Connections

Raspberry Pi w/ RS-485 Adapter

libmodbus makes communication simple; basically open the serial port and read addresses.  A little test program I wrote verified that things are working:

# ./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

Ok!  So to get the pretty graph above, I obviously needed some data collection; for this I used collectd simply because it already had a ModBus plugin.  It did require some patching and hacking though; the non-bloggy details are on this page; the graph you see above was made by Visage.

Overall this has been very useful; the boiler controls are fairly involved, but primarily we want to get the outdoor reset curve tweaked so that we get nice long runtimes and low return temps, which keep the boiler in its most efficient condensing mode.  My installer hit the default buttons and left, as contractors often do.  Seeing how the boiler was working over time definitely helped me tweak things to improve performance, efficiency and comfort.  Primarily, I drastically lowered the high end of the outdoor reset curve which defaulted to 170F For cast iron radiators, because we have much more radiation in this house than it needs at 170F output.  I currently have the high end set to 140F.  I may do another post on all that later… suffice it to say, doing a heat loss and measuring radiation and using that information to guide boiler setup is not rocket science, and you really need to do it for the sake of comfort and efficiency.

collectd, at least with the rrd output, doesn’t keep fine-grained data around for long.  The current setup is useful for seeing how things behaved in the past couple days, but I’ll probably start logging fine-grained data to a database at some point, and can start doing fun things like using the firing rate to estimate gas usage over the month, look at therms per heating degree days in near real time, etc.

Other boilers have ModBus interfaces as well; in particular Lochinvar and NTI both mention the capability, although I haven’t yet found documentation on what data is available.  If you have a boiler with ModBus and want to try this hack, drop me a line – I’d love to see how this looks in other homes, and I’d be glad to help you set up a collectd config file.  I may see about creating a Raspberry Pi image to make this all more or less work out of the box.

Again, take a look at the the more detailed writeup for a bit more info, in particular the patches I used with the upstream tools, and if you try this at home & make it work, share what you find!

34 thoughts on “Residential boiler monitoring via ModBus

  1. Hey this looks promising! I have that same boiler and want to use more of the built in functions such as this… later on the exported information would be so nice to monitor.
    Also, I will be adding a wood boiler in parallel (ideally the gas source being backup only), so I wonder if somehow using the logic in the Trimax control could export and inform the pumps and relays on the wood side to mimic the precision of the gas boiler without having to buy and install redundant controls…?

  2. I’d like to try this but my boiler does not currently support modbus. It’s a Weil-McLain HE II and the control board is a Gold GV Model 1013-200. Any suggestions on a control board I could swap in that would support modbus readout?

    • Hey Adam – I know very little about other boilers, sorry. My old boiler was a Weil Mclain, though – super basic, no smarts at all, and I set up an Electric Imp with LM75 temp sensors to do some data logging of supply and return temperatures. Not quite as slick, but it was still interesting.

  3. Hi Eric, I’m tinkering around with this great tutorial. I have a new TT PT-60 boiler I recently installed. But I’m having problems with the tt-status utility. It’s not able to read anything from addr 0. Failure happens at “Error: Modbus read of 1 byte at addr 0 failed”

    The error happens using the same USB-> RS485 adapter you used at /dev/ttyUSB0 and also a RaspiComm that appears as /dev/ttyRPC0. For development purposes I’m using a modbus slave simulator program on Windows instead of the actual boiler. Maybe that’s the problem?

    • I assume that it must be the simulator that’s the problem; I don’t know how you set it up and define the available registers, but that might be the problem.
      There are at least 2 modbus register types, input and holding. Perhaps you need to be sure the simulator is set up so that address 0 is an input register?

  4. I’m thinking it might be related to the simulator too. I’m monitoring the raw serial data at the moment. You can see here I run tt-status 13 times before it finally responds on the 000015 line. I have no idea why the receive packets are coming through differently on lines 0 thrugh 14.

    000000-Rx:01 D0 00 01 4C FC
    000001-Rx:01 D0 00 01 4C FC
    000002-Rx:01 D0 00 01 4C FC
    000003-Rx:01 D0 00 01 4C FC
    000004-Rx:01 D0 00 01 4C FC
    000005-Rx:01 D0 00 01 4C FC
    000006-Rx:01 D0 00 01 4C FC
    000007-Rx:01 D0 00 01 4C FC
    000008-Rx:01 D0 00 01 4C FC
    000009-Rx:01 D0 00 01 4C FC
    000010-Rx:01 D0 00 01 4C FC
    000011-Rx:01 D0 00 01 4C FC
    000012-Rx:01 D0 00 01 4C FC
    000013-Rx:01 D0 00 01 4C FC
    000014-Rx:01 04 03 00 00 01 31 8E
    000015-Tx:01 04 02 01 F4 B9 27
    000016-Rx:01 04 03 00 00 09 30 48
    000017-Tx:01 04 12 01 F4 01 90 00 7D 00 7D 00 64 00 00 00 64 00 19 00 7D 86 3B
    000018-Rx:01 03 05 00 00 02 C4 C7
    000019-Tx:01 83 02 C0 F1

  5. It turns out that it was indeed just a terrible emulator. I downloaded a trial of WinModBus and it’s working perfectly. Now to configure the collectd values and post them to my web server.

    Thanks for the work you put into this.

  6. I’m running into troubles trying to get your .deb files with the patches to collectd’s modbus plugin working properly. Seems like it doesn’t want to connect to /dev/ttyUSB0 or /dev/ttyRPC0. The collectd log file is reporting:

    Modbus plugin: Creating new Modbus/TCP object failed.
    Modbus plugin: mb_init_connection ([hostname]/) failed.

    I’ve modified the collectd.conf file’s Host section to point to:
    Device “/dev/ttyRPC0”
    Baudrate 38400
    Interval 60

    Not sure what to jump into next. So close )

  7. BTW, I’m using WinModMBus to watch the modbus serial traffic and emulate the boiler’s registers. Very cool program.

  8. Just an update: I was thinking that maybe the version of Rasbian that I’m using on the RPI may have updated/overwritten the modbus colletcd plugin from your patched debs. So I uninstalled all packages related to collectd and then reinstalled your debs. Still having the same problem.

    I think the next step is to try to install collectd from source and apply your patches manually. Think that makes sense?

    Btw, I see your patches will be included in the next release of collectd. Nice!

  9. I’ve done a little more debugging on this issue. Not sure what’s causing host->conntype to be set as MBCONN_TCP.

    In my collectd.conf I have the following:

    Device “/dev/ttyUSB0”
    Baudrate 38400
    Interval 60

  10. Oops didn’t mean to post so soon.

    If I remove the Baudrate line then it will throw the error for “No serial baudrate has been specified” while parsing the config file. So at least at that point it’s thinking we have a MBCONN_RTU.

    For some reason mb_init_connection is following the logic for host >conntype == MBCONN_TCP instead of the else block.

    Still haven’t figured out why.

  11. I’ve been playing with the order of the items in modbus.conf

    If I use the ordering of your suggested additions modbus.conf. (data nodes first followed by the Host section) This is what the error log looks like on a colelctd service restart. Note it throws an error for each Data section.

    [2015-04-09 13:04:14] Exiting normally.
    [2015-04-09 13:04:14] collectd: Stopping 5 read threads.
    [2015-04-09 13:04:14] collectd: Stopping 5 write threads.
    [2015-04-09 13:04:16] Modbus plugin: Creating new Modbus/TCP object failed.
    [2015-04-09 13:04:16] Modbus plugin: mb_init_connection (localhost/) failed.
    [2015-04-09 13:04:16] Modbus plugin: Creating new Modbus/TCP object failed.
    [2015-04-09 13:04:16] Modbus plugin: mb_init_connection (localhost/) failed.
    [2015-04-09 13:04:16] Modbus plugin: Creating new Modbus/TCP object failed.
    [2015-04-09 13:04:16] Modbus plugin: mb_init_connection (localhost/) failed.
    [2015-04-09 13:04:16] Modbus plugin: Creating new Modbus/TCP object failed.
    [2015-04-09 13:04:16] Modbus plugin: mb_init_connection (localhost/) failed.
    [2015-04-09 13:04:16] Modbus plugin: Creating new Modbus/TCP object failed.
    [2015-04-09 13:04:16] Modbus plugin: mb_init_connection (localhost/) failed.
    [2015-04-09 13:04:16] Modbus plugin: Creating new Modbus/TCP object failed.
    [2015-04-09 13:04:16] Modbus plugin: mb_init_connection (localhost/) failed.
    [2015-04-09 13:04:16] Modbus plugin: Creating new Modbus/TCP object failed.
    [2015-04-09 13:04:16] Modbus plugin: mb_init_connection (localhost/) failed.
    [2015-04-09 13:04:16] read-function of plugin `modbus-localhost’ failed. Will suspend it for 120.000 seconds.
    [2015-04-09 13:04:16] Initialization complete, entering read-loop.

    If I change collectd.conf so the Host is the first section the error log looks like this:

    [2015-04-09 13:12:39] Exiting normally.
    [2015-04-09 13:12:39] collectd: Stopping 5 read threads.
    [2015-04-09 13:12:39] collectd: Stopping 5 write threads.
    [2015-04-09 13:12:41] read-function of plugin `modbus-localhost’ failed. Will suspend it for 120.000 seconds.
    [2015-04-09 13:12:41] Initialization complete, entering read-loop.

    Here’s my conf file:

    # Config file for collectd(1).
    #
    # Some plugins need additional configuration and are disabled by default.
    # Please read collectd.conf(5) for details.
    #
    # You should also read /usr/share/doc/collectd-core/README.Debian.plugins
    # before enabling any more plugins.

    ##############################################################################
    # Global #
    #—————————————————————————-#
    # Global settings for the daemon. #
    ##############################################################################

    Hostname “localhost”
    #FQDNLookup true
    BaseDir “/var/lib/collectd”
    PluginDir “/usr/lib/collectd”
    #TypesDB “/usr/share/collectd/types.db” “/etc/collectd/my_types.db”

    #—————————————————————————-#
    # When enabled, plugins are loaded automatically with the default options #
    # when an appropriate block is encountered. #
    # Disabled by default. #
    #—————————————————————————-#
    #AutoLoadPlugin True

    #—————————————————————————-#
    # Interval at which to query values. This may be overwritten on a per-plugin #
    # base by using the ‘Interval’ option of the LoadPlugin block: #
    # #
    # Interval 60 #
    # #
    #—————————————————————————-#
    Interval 20

    Timeout 2
    ReadThreads 5
    WriteThreads 5

    # Limit the size of the write queue. Default is no limit. Setting up a limit
    # is recommended for servers handling a high volume of traffic.
    #WriteQueueLimitHigh 1000000
    #WriteQueueLimitLow 800000

    ##############################################################################
    # Logging #
    #—————————————————————————-#
    # Plugins which provide logging functions should be loaded first, so log #
    # messages generated when loading or configuring other plugins can be #
    # accessed. #
    ##############################################################################

    LoadPlugin logfile
    LoadPlugin syslog

    LogLevel “info”
    File “/var/log/collectd.log”
    Timestamp true
    PrintSeverity false

    LogLevel info

    ##############################################################################
    # LoadPlugin section #
    #—————————————————————————-#
    # Specify what features to activate. #
    ##############################################################################

    # Load required plugins:
    LoadPlugin modbus
    LoadPlugin match_regex

    # Load required targets:
    LoadPlugin target_scale

    Device “/dev/ttyRPC0”
    Baudrate 38400
    Interval 60

    Instance “Boiler”
    Collect “supply-temp”
    Collect “return-temp”
    Collect “dhw-temp”
    Collect “flue-temp”
    Collect “outdoor-temp”
    Collect “firing-rate”
    Collect “boiler-setpoint”

    RegisterBase 768
    RegisterCmd ReadInput
    RegisterType Int16
    Type temperature
    Instance “supply”

    RegisterBase 769
    RegisterCmd ReadInput
    RegisterType Int16
    Type temperature
    Instance “return”

    RegisterBase 770
    RegisterCmd ReadInput
    RegisterType Int16
    Type temperature
    Instance “dhw”

    RegisterBase 771
    RegisterCmd ReadInput
    RegisterType Int16
    Type temperature
    Instance “flue”

    RegisterBase 772
    RegisterCmd ReadInput
    RegisterType Int16
    Type temperature
    Instance “outdoor”

    RegisterBase 775
    RegisterCmd ReadInput
    RegisterType Int16
    Type percent
    Instance “firing-rate”

    # Note – setpoint is “32768” (invalid) if boiler is idle.
    # I can’t figure out how to make this match in chains/rules
    # below, so I simply changed the temperature type to only
    # be valid from -100 to +200 and this value gets ignored.
    # Bleah.

    RegisterBase 776
    RegisterCmd ReadInput
    RegisterType Int16
    Type temperature
    Instance “setpoint”

    PreCacheChain “PreCache”

    # Boiler supply is in 0.1 degrees C; convert to C

    Plugin “^modbus$”
    Type “^temperature$”
    TypeInstance “^supply$”

    Factor 0.1

    # Boiler temps are in degrees C

    Plugin “^modbus$”
    Type “^temperature$”

    # F = (C * 9) / 5 + 32

    Factor 9

    Factor 0.2

    Offset 32

    Filter “*.conf”

  12. Just to update all of this for people that might read it in the future. I was able to get everything working using my Rasipcomm serial interface at /dev/ttyRPC0. (no TCP to RTU daemon necessary.) I had to download the sources, apply the patches and compile/install everything. Works very well. Now to figure out how I’m going to use the data.

    Thanks for all your help Eric and for doing the work on making this possible.

  13. Hi Eric,

    I like your article. I’m currently trying to use the same Kedsum USB 485 device in a rpi to talk to a piece of equipment that wants 9600 Baud, 7 Bits, Even Parity, 1 Stop Bit. What I have found is that if I put the kedsum in my laptop, and apply those settings, I can talk to the equipment just fine, but when I put the kedsum in the rpi and use stty to configure it I just get garbage, even though stty will report that the settings were applied. Did you have to load your own driver to the rpi for the kedsum to work properly?

    Thanks,
    Doug

    • I just plugged it in and it worked … how are you “talking” to it? Did you try my little ttstatus.c test program on the rpi?

  14. Thanks for putting this all together!! As I am a year behind, some things seem to not be working. I ordered what *should* be the same dongle (lsusb reports:
    Device 004: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter) Can you confirm that is the same dongle you have?

    When I run the tt-status, I am thrown this error:
    “Error: Modbus read of 1 byte at addr 0 failed”

    I’ve checked the wiring and the boiler is set to address 1. Any ideas? Also the tt-status.c has a minor error that needs to be corrected prior to compiling by usining the correct comment code:
    #warning slave ID needs to be an option too

    A little late to the game, but from what I can surmise, collectd has merged in your changes (I’m running collectd 5.4.1). syslog has these errors in it:
    Modbus plugin: Unknown configuration option: RegisterCmd
    Modbus plugin: Data block “supply-temp”: No type has been specified.

    Any help would be appreciated as I was planning on using this as my cornerstone of my home energy management system.

    Thanks

    • Mine is also:
      Bus 001 Device 005: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
      As for the warning in tt-status.c, that’s just supposed to throw a compile-time error; it was a warming to myself to fix that later.

      Collectd did merge patches, but some options changed since the original version I was running locally. I’ts now i.e. –

      RegisterBase 768
      RegisterType Int16
      # RegisterCmd ReadInput <-- that was the old way
      ModbusRegisterType input
      Type temperature
      Instance "supply"

      ....
      I’ll send you an email, and see if I can help you get things sorted.

      • I am having the same error, did anyone have any insight?

        The collectd status gives the following information:
        Modbus plugin: Data block “flue-temp”: No type has been specified.
        Modbus plugin: Unknown configuration option: RegisterCmd
        I get the same two errors for a few data blocks (but not all).

        I’ve tried “RegisterCmd ReadInput” and “ModbusRegisterType input”, but there was no change.

        running tt-status.c gives expected results, without errors.
        also Eric, thank you for all your work here. This is great!

        • Hi Ben – I really should update this post now that all the modbus stuff is merged in current upstream collectd. I’ll send you an email, and when we get it sorted properly I’ll drop another comment here & update the post as needed.

          • After a year, we’ve been able to get this up and running. The fix seems to be the 3rd dongle I ordered (reports the same as the 1st). I’ve also implemented wunderground data using collectd’s curl-json to more accurately report temperature as the outdoor sensor that came with my boiler is always 5-10 degrees high.

            Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *