Pentair Compool Hacking

The Discovery

One weekend in the very late summer my family and I went in our backyard pool to enjoy one of the last chances of the season to splash around in the backyard. The water was a little cooler than expected and upon investigating we found that the solar heating had not been working and that there was a mysterious "ERR 1" displayed on our pool control panel in our house.

Given that this was a relatively new controller purchased this millennium, I was a little disappointed by the cryptic error message, but also hopeful that I would be able to find out something about it online. Sure enought, I found the Compool Cp3800 manual on the Pentair website. It explained that "Err 1" was a problem with the water temperature sensor, which was a plausible reason why the solar heat was not turning on.

I learned a lot of other things from the manual than just how to diagnose error codes. For example, there was support for a wired spa-side remote as well as a telephone interface. One frustrating thing about our current pool was that when we remodeled the pool, the designer omitted a spa-side remote, something we used to have and use. We did not realize this ommision until it was too hard to easily correct. They suggested the workaround was to replace our brand new and expensive pool control with an even more expensive model that would support a wireless controler. Not wanting to spend more money with this frustrating vendor who had not thought out the use cases very well, we just left the system as it was, even though it meant occasionally walking though the house wet to access the control panel to turn the spa on.

With all the Pentair Compool information available, I was able to confirm that a new controller would be required for the wireless controller. However, it occured to me that whatever they were using to connect the spa side remote or the telephone interface could be interfaces for a remote of my own creation. Indeed, the manual included a wiring diagram showing how the spa remote's switches could be made to control the users choice of any four of 9 total options as well as a tenth spa-side remote specific "heat boost" option not found on the main control panel which had more precise temperature controls. There was separate documentation on the MOD-TELSPA telephone remote control, which allows a user to call to their home phone and dial SPA or HTR to turn on the spa or heater respectively. The telephone module seemed a more restrictive interface with a less documented connector to the power center circuit board. However, alongside the documentation was information on another extension module for the Compool Cp3800 called the MOD-RS485.

The MOD-RS485 consisted of a RS-232 to RS-485 converter and Softcp Windows software that provided a virtual version of the Compool control panel. RS-485 was the communication bus being used to connect the main control panel in our house to power center circuit board. The module plugged into a second port on the back of the indoor control panel allowing a computer to be plugged into the RS-232 (aka serial) port. While the MOD-RS485 module cost hundreds of dollars, I could imagine that it might not be too hard to reverse engineer the protocol. Since this would be the same protocol used between the main control panel and the power center circuit board, a program using this would have full access to the system including not just control, but also status such as temperature as well error reports. Given a computer interface, I could easily build a web interface to control the pool from a phone browser or from one of my Wi-Fi phones connected to my Asterisk home PBX system.

Not wanting to reinvent the wheel, I searched to see if someone else had already documented the Cp3800 protocol. I quickly found a thread entitled Compool Driver - PERL question referring not only to Perl code for talking to a Compool controller, but also a specification for the Compool interface written by a Visual Basic programmer. Looking to see if I could find the Perl code online myself, I discovered MisterHouse, an open source home automation system for Windows or Unix written in Perl. Sure enough, MisterHouse's feature list mentioned Compool pool equipment, with the FAQ noting that support was added back in May 2000 and ComPool updates as recently as January 2003. I downloaded the latest MisterHouse source code to confirm there ComPool support would work with my particular ComPool Cp3800 model. The Compool module, which is found lib/Compool.pm, did in fact mention a serial interface, referenced LX3xxx and a model 3830 controller, had the Perl code mentioned in the forum thread I had found and the header comment matching the top of protocol specification (local cleaned up copy) provided by the Visual Basic programmer on that thread. Seemed like a pretty solid match for my Cp3800. In 2023, Dave Jaggar provided me a more complete reference for Compool LX3xxx Units. Searching for LX3xxx found Crestron offers some free integrations for Compool controlers that have some protocol information.

I pulled my Cp3800 control panel off the wall and confirmed that there was a free RJ25 jack to connect an an RS-485 to RS-232 convertor. These converters could be found for less than $10 online including shipping, so after confirming I could not get one locally at someplace like Fry's or Halted, I ordered one and waited for it to arrive.

First Contact

After the RS-485 to RS-232 adapter arrived, I needed to figure out the right RJ25 pinout to connect the ComPool control panel to the adapter. Unlike RS-232 which has a number of well defined connectors including DB-25 and DE-9 (aka DB-9), there are no standard connectors or pinouts for RS-485. However, the wiring is much simpler consisting of a positive and negative signal on two wires plus an optional ground.

My RS-485 to RS-485 adapter in the Hexin HXSP-485B. The also have two other models 485A and 485C, but all that changes is wiring block attached to DE-9 connector on the RS-485 side. The 485B seems to be the most compact and seems to be the most common on eBay. The 4 wire connections are labeled from left to right:

LabelActualUser Manual
D+/A D-/A Data-
D-/B D+/B Data+
GND GND GND
9V 9V +5~+12V

The GND and 9V are for supplying power to the adapter, although in typical operation the adapter can get its power from the RS-232 port.

The D+/A and D-/B are the positive and negative data pins. The A/B naming seems to be backward from the standard, which is a common mistaken apparently. (It turned out I found out the hard way that the A/B labels were correct and the D+/D- labels were what was backwards. The HXSP-485 User Manual does seem to imply the correct order, although the HXSP-485B Data Sheet does not have any pinout and in fact cannot even spell data correctly.)

The wiring on the ComPool side was a little less clear. In the wall behind the ComPool control panel was a RJ25 wiring box labeled "iS10 WIRING". I had seen the name iS10 on the Pentair web site along side the other automation documentation so I went back and found the iS10 Spa-Side Remote, which provides a spa-side remote with more advanced temperature controls for InterTouch pool control systems, but also with an "adapter" box for the Cp3800.

The iS10 guide documented the RS-485 signals as:

Color Signal
BLK GND
GRN -DATA
YEL +DATA
RED +15 VDC

The iS10-to-Cp3800 adapter turned out just to be a RJ25 wiring block. It used an alternate old coloring standard for RJ25 pinouts used six colors:

PinColor
1 white
2 black
3 red
4 green
5 yellow
6 blue

The iS10 spa-side remote documenation provided the following mapping into RJ25 colors

iS10 Cp3800
RED YEL
YEL RED
GRN GRN
BLK WHT

Pulling this all together, it seemed like the RJ25 6P6C connector on the back of the Cp3800 could be mapped as follows:

PinOld colors Twisted pair colors Signal
1 white white/green GND
2 black white/orange unused
3 red blue +DATA
4 green white/blue -DATA
5 yellow orange +15 VDC
6 blue green unused

Later I noticed that I worked harder than I had too because the protocol description already had a similar, although slightly different, pin out:

PinColor Signal
1 white GND
2 black +10 VDC (~100ma max)
3 red +DATA
4 green -DATA
5 yellow+10 VDC (~100ma max)
6 blue GND

Since for my for my first test I was only going to try and use the +DATA -DATA signals without a ground, I took an old RJ14 cable I used in testing jacks when setting up my Asterisk home PBX system and wired the red and green wires to the matching D+ and D- pins on HXSP-485B adapter (Note that as I discovered later red needed to go to D-/A and green to D+/B). The next step was to take my old Dell Inspiron 8200 laptop which had an actual serial port on it and connect up the HXSP-485B and see if I could see anything with a terminal program such as Microsoft Windows HyperTerminal (or in my case Van Dyke Software's SecureCRT, which just added a dumb terminal type to version 6.6 at my request after I found that it didn't support that for this type of debugging). The MisterHouse ComPool::init method (in file lib/Compool.pm) suggested using serial port settings of 9600 baud with 8 data bits, no parity, and 1 stop bit. I connected and saw chunks of data arrive every 2.5 seconds as noted in the protocol description, with a common 23-byte chunk repeating frequently:

00 a9 95 c9 fb df f3 d9 ff bf 9f 6d bf ff ff e7 39 b8 ff 1f ea 43 fe

Looking again at ComPool::init, it seems that the protocol was in fact binary with a basic acknowledgement packets being 24 bytes long. The protocol description agreed. so something seemed wrong with my data. Interestingly, the basic acknowledgement packet should start with "ff", not "00". Perhaps the data wire polarity was reversed?

After switching the data wires, I now see a 24-byte pattern starting with the expected "ff aa 0f" sequence:

ff aa 0f 1b 02 10 3b 13 00 20 30 46 1c 00 00 8c 8d 1d 00 00 f0 80 05 8b
ByteDescription Value
0 SYNC BYTE 0 0xff (always)
1 SYNC BYTE 1 0xaa (always)
2 Destination Address 0x0f (always)
3 LX3xxx Version 0x1b - 27 decimal - version 2.7
4 OpCode 0x02 (always)
5 Info Field Length 0x10 (always)
6 Minutes 0x3b - 59 decimal - 59 minutes
7 Hours 0x13 - 19 decimal - 7pm
8 Primary Equipment 0x00 - 0b00000000 - Spa, Pool, Aux 1-6 all off
9 Secondary Equipment 0x20 - 0b00100000 - Solar Present (bit 5), Fahrenheit display, others off
10 Delay/Heat Source 0x30 - 0b00110000 - Pool Solar heat only, Spa heat off
11 Water Temperature 0x46 - 70 decimal - 70/4 degrees Celsius - 63.5 degrees Fahrenheit
12 Solar Temperature 0x1c - 28 decimal - 27/2 degrees Celsius - 56.5 degrees Fahrenheit
13 Spa Water Temp 0x00 (3830 only)
14 Spa Solar Temp 0x00 (3830 only)
15 Desired Pool Temp 0x8c - 140 decimal - 140/4 degrees Celsius - 95 degrees Fahrenheit
16 Desired Spa Temp 0x8d - 141 decimal - 141/4 degrees Celsius - 95.45 degrees Fahrenheit
17 Air Temperature 0x1d - 29 decimal - 29/2 degrees Celsius - 58.1 degrees Fahrenheit
18 Spare/Future Use 0x00
19 Spare/Future Use 0x00
20 Equip/Sensor Stat 0xf0 - 0b11110000 - Freeze Protection, Air Sensor, Solar Sensor, Water Sensor
21 Product Type/Stat 0x80 - 0b10000000 - 3800 System
22 Hi Byte of Checksum 0x05 - hi byte of 0x058b
23 Lo Byte of Checksum 0x8b - lo byte of 0x058b
All the data decoded as expected! The time and air temperature are even right. I can see this could be useful for more than just pool monitoring. Very cool.

Control! Control! You Must Learn Control!

Now that I seemed to have a working data connection, it was time to try and get a interface to the pool going. Since MisterHouse seemed ready to go, I figured I might as well try that to start.

  1. Followed the Mister House Quick Install Instructions for Windows Users

  2. Created C:\misterhouse\mh.private.ini, C:\misterhouse\code, and C:\misterhouse\data as suggested under Coding your own events. My mh.private.ini has the following contents:

    code_dir = C:/misterhouse/code
    data_dir = C:/misterhouse/data
    Compool_serial_port = COM1
          
  3. Create an empty directory C:\misterhouse\data\logs

  4. Create a C:\misterhouse\run.bat script to run mh with the proper mh_parms:

    @echo off
    set mh_parms=c:\misterhouse\mh.private.ini
    c:
    cd \misterhouse\mh\bin
    call mh
    cd \misterhouse
          
  5. Create a C:\misterhouse\data\code_select.txt to specify a more minimal set of modules than the default:

    mh_control.pl
    tk_frames.pl
    tk_widgets.pl
    bdc_compool_test.pl
          

    Here is a brief description of why I included each of these modules:

    mh_control.pl
    Core MisterHouse commands (reload, etc)
    tk_frames.pl
    Specifies Tk UI layout
    tk_widgets.pl
    Specifies Tk UI contents
    bdc_compool_test.pl
    My Compool code (see below)
  6. Created C:\misterhouse\code\bdc_compool_test.pl. I looked at mh/code/public/Compool.pl and mh/code/public/Compool_test.pl for examples and mh/lib/Compool.pm for interface details.

  7. Created a Windows Scheduled Task with Control Panel to startup C:\misterhouse\run.bat on boot so it would automatically come up after Windows Update reboots, etc.

Pretty Pictures

One of the features of bdc_compool_test.pl is that on every $New_Minute it checks the pool status which is logged to a CSV file. A perl script on my home Linux box runs every minute via cron using rsync to pull the data, ploticus to generate the graphs, and another rsync to upload the results to my public pool temperature page.

A Kindler, Gentler Web Interface

Although MisterHouse has a web interface, it was not something I wanted to family to have to use poolside on a small phone screen. Fortunately, the MisterHouse web interface did automatically expose controls for my bdc_compool_test.pl code, including checking the current status of pool equipment, sensors, etc. So I put together this simple old-school Perl CGI script to provide a minimal interface, dropping it into a http://pool virtual host for convenience.

Moving from Windows to Unix

With everything basically working, I wanted to move off the Windows laptop which I used for the proof of concept to the home Linux box I planned to use for the long term. Most of this involved pulling wire from the Linux box to the pool controller, but I'll document the Linux setup here.

  1. Followed the Mister House Quick Install Instructions for Unix Users. Specifically:
    1. mkdir ~/misterhouse
    2. cd ~/misterhouse
    3. wget http://prdownloads.sourceforge.net/misterhouse/misterhouse-2.105.tar.gz
    4. tar xzvf misterhouse-2.105.tar.gz
    5. cd mh/bin
    6. ./configure
  2. Created C:/misterhouse/code, and ~/misterhouse/data, ~/misterhouse/data/logs, ~/misterhouse/run, ~/misterhouse/data/code_select.txt, ~/misterhouse/code/bdc_compool_test.pl along the lines of the Windows versions above. I did not use a mh.private.ini on Linux since it could not handle expanding ~/misterhouse paths and I did not want to hardwire my home directory into the configuration file. So instead I supplied the options on my ~/misterhouse/run script like this:

    The Linux version of my ~/misterhouse/run script looks like this:

          #!/bin/bash
          export mh_parms=$HOME/misterhouse/mh.private.ini
          cd $HOME/misterhouse/mh/bin
          ./mh \
              -code_dir $HOME/misterhouse/code \
              -data_dir $HOME/misterhouse/data \
              -gd 0 \
              -tk 0
        

    The "-gd 0" avoids a warning because of a missing GD Perl package.

    The "-tk 0" disables the Tk user interface for console use.

    Unfortunately, it misterhouse would not let me supply the Compool_serial_port on the command line, presumably because it was not found in mh/bin/mh.ini, so I keep that in the ~/misterhouse/mh.private.ini like so:

    Compool_serial_port=/dev/ttyS0
          
  3. Migrated my historical data from the old Windows box to the new Linux box:

    rsync -a -v -P windows-machine-name:/cygdrive/c/misterhouse/data/pool-data-*.csv ~/misterhouse/data/
          
  4. Minimally ComPool.pm to avoid using Win32::SerialPort specific code, instead using the portable Device::SerialPort subset. The full file is here, but the change was pretty small, basically removing use of is_handshake, changing is_status to status, and collapsing read_bg/read_done into a simple read call. Here is the diff:

    $ diff Compool.pm.orig Compool.pm
    76,77d75
    <     $serial_port->is_handshake("none");         #&? Should this be DTR?
    < 
    633c631
    <     (my $BlockingFlags, my $InBytes, my $OutBytes, my $LatchErrorFlags) = $serial_port->is_status || warn "could not get port status\n";
    ---
    >     (my $BlockingFlags, my $InBytes, my $OutBytes, my $LatchErrorFlags) = $serial_port->status || warn "could not get port status\n";
    677,689c675,676
    <     my $got = $serial_port->read_bg ($wanted);
    < 
    <     if ($got != $wanted) 
    <     {
    <        	# Abort
    <         $serial_port->purge_rx;
    <         $serial_port->read_done(0);	
    <     }
    <     else 
    <     { 
    <         ($ok, $got, $result) = $serial_port->read_done(0); 
    <     }
    <     return $got ? $result : "";
    ---
    >     my ($got, $result) = $serial_port->read ($wanted);
    >     return ($got > 0) ? $result : "";
          
  5. Setup misterhouse to automatically start at boot time. I used the instructions from the StarterScripts page of MisterHouse Wiki, not the ghastly version from the FAQ. I tweaked the /etc/init.d/mh script to run as my personal user, not mh, and out of my ~/misterhouse, not /opt/misterhouse. I also fixed a bug in the mh.pid path, which is actually located in misterhouse/mh/data/mh.pid, not misterhouse/data/mh.pid. I also changed their mh.sh script by locating it in ~/misterhouse/mh.sh and had it invoke my ~/misterhouse/run script instead of misterhouse/mh/bin/mh directly. I used "rcconf --on mh" to make sure it started on next boot.

    UPDATED: now on Ubuntu 10.04 Lucid Lynx I'm using an Upstart script in /etc/init/mh.conf.

  6. Based on warning in the ~/misterhouse/data/mh.log, ran ~/misterhouse/mh/bin/set_password to setup a misterhouse password.

MisterHouse Issues

I have to say that MisterHouse is not very cleanly architected. Here are some things I found suprising or frustrating:

  • The code appear to be plain perl at first glance but are actually parsed to differentiate code that runs only at startup (such as object creation) while the rest is left to run in an event loop.

  • Like asterisk distributions, a whole lot of sample things are turned on out of the box to show you how the powerful the system is. Unfortunately it was not very clear how to disable things to go to a more minimal configuration because things like the Tk UI were also enabled as part of the same extension mechanism. Clever perhaps, but it meant experimenting to turn off unneeded features.

  • It is good that they encourage the user in the documentation with startup warnings that users should maintain their own code and data directories. However, their own use of the directories is confusing because they both contain a mixture of maintained and generated files. I'm not talking about the fact that config files can be editted by hand with a text editor versus through the Tk and web interfaces. I'm talking about the fact that derived files such as items.mhp are stored alongside their sources such as items.mht. Or that generated code such as organizer_events.pl and organizer_tasks.pl are dumped in code because organizer.pl by default. As a new user of the system, I like to look over the configuration files and having all these extraneous files in my code and data directories as opposed to some "/var" like area was confusing.

  • "parm" should not be an abbreviation for "parameter"...

Bugs to report:

  • "indpendent" misspelled in "How are the X10 and Serial Items implemented, indpendent of the platform?"

  • Need to replace "get_device_now" with "get_device" in Compool code and lib

  • Need to replace "HomeBase" with "Compool" in mh/code/public/Compool_test.pl

  • Reference to unknown "Compool::set_device_with_timer" found in mh/code/public/Compool.pl

  • mh/lib/Compool.pm has Win32::SerialPort specific calls. For example, "is_handshake" is used which is similar to commented out code in mh/lib/Xantect.pm. Similarly, the Win32 specific "is_status" is used instead of "status" and "read_bg/read_done" instead of "read".

  • wrapper script recommendation should suggest using "call mh" not just "mh" and perhaps change directory after

  • "mh.private.ini" overwritten with theme data (automatic backup was available but don't write my private file)

  • Does not support ~ expansion in mh.private.ini (or presumably mh.ini)

  • Warns that I have not set code_dir and data_dir even when I have on command line.


Brian D. Carlstrom [email protected]