A Typescript/CommonJS/ESModule library for interfacing with Pentair IntelliCenter pool controllers on your local network. To be able to discover local controllers, your network must support UDP broadcasts.
Tested with an IntelliCenter system on version IC: 1.064 , ICWEB:2021-10-19 1.007.
The Wiki has development notes on how the IntelliCenter API works under the hood.
See example.ts for an example of interfacing with the library. Broadly, import the library with
// ESM
import { FindUnits, Unit } from "node-intellicenter";
import * as messages from "node-intellicenter/messages";
// CJS
const { FindUnits, Unit } = require("node-intellicenter");
const messages = require("node-intellicenter/messages");
Then if you want to search for controllers on the network, create a new IntelliCenter unit finder with:
const finder = new FindUnits();
And search for units:
// async/await
const units = await finder.searchAsync();
const unitInfo = units[0];
// or event-based
finder.on("serverFound", (unitInfo) => {
console.log(unitInfo);
});
finder.search();
This performs a UDP mDNS broadcast on 224.0.0.251, port 5353, so ensure your network supports UDP broadcasts and the device is on the same subnet.
If you have multiple network interfaces, you can be explicit about the one you want used for searching by passing its address to FindUnits
, e.g.: new FindUnits("10.0.0.3")
.
When a unit is found, connect to it by creating a new Unit
with
const unit = new Unit(unitInfo.addressStr, unitInfo.port);
or if you want to bypass the searching process because you know the address of your unit:
const unit = new Unit("10.0.0.41", 6680); // substitute your unit's address here. port should generally be 6680 and will default to that if not provided.
Call connect()
on the Unit instance:
// async/await
await unit.connect();
console.log("connected!");
// promises
unit.connect().then(() => {
console.log("connected!");
});
// or event-based
unit.once("connected", () => {
console.log("connected!");
});
unit.connect();
Once you've connected, there are a number of methods available for interacting with the controller. See API reference below.
All communication with an IntelliCenter unit is done via TCP (WebSockets).
Send a request and handle the response with:
const response = await unit.send(messages.GetSystemConfiguration());
console.log(JSON.stringify(response, null, 2));
See the example script (transpiled CJS version | ESM version) for some code examples to get you going.
There is a list-objects script available for easily identifying objects on your controller that you can use to get info from or set attributes on.
When invoked with npm run list-objects
or node esm/list-objects.js
it will search for units, then request and format+display the objects to the user. There are a few arguments available:
--controllerAddr=1.2.3.4
- Specifies the IntelliCenter controller's address directly which skips the searching phase. Substitute your controller's address for 1.2.3.4 here.
--controllerPort=1234
- Specifies the port to use when connecting to the controller (you generally should never need this; does nothing if
--controllerAddr
is not specified). Substitute your controller's port for 1234 here.
- Specifies the port to use when connecting to the controller (you generally should never need this; does nothing if
--multicastAddr=1.2.3.4
- Specifies the address of the network interface to send the multicast search packet on (useful if you have multiple adapters/interfaces and the system is picking the wrong one; does nothing if
--controllerAddr
is specified). Substitute your interface's address for 1.2.3.4 here.
- Specifies the address of the network interface to send the multicast search packet on (useful if you have multiple adapters/interfaces and the system is picking the wrong one; does nothing if
--onlyToggleable
- Only displays objects which can be toggled on or off.
Note that if you are invoking this with npm run list-objects
then the arguments must be specified after an empty --
so that they are given to the script rather than npm itself. Example: npm run list-objects -- --controllerAddr=10.0.0.41 --onlyToggleable
Contributions are welcomed! Please open a pull request with your changes and let's discuss!
See the list of messages for an up-to-date central listing of all available messages. These are essentially all helper methods for well-known messages, but you can construct your own by creating a new ICRequest via either the GetRequest() helper method or just constructing a new ICRequest()
and filling it out (note: if you go the raw ICRequest route, you'll want some method of handling messageID that allows you to distinguish responses from each other).
IntelliCenter controllers can handle lots of messages being thrown at them at once and will respond when each request has been processed. This is why the messageID
field on a request is important. If you use this library in async/await mode you'll only be dealing with one request in flight at a time, but that's not technically necessary.
Messages (requests and responses) are sent in JSON format. Generally a request is made with a command
, an optional condition
, and an objectList
with details about what properties ("keys") and objects are being referred to. Some commands, such as GetQuery
, require additional properties to be specified.
See ICRequest and ICResponse as well as ICParam for all known fields.
There are some redacted connection logs available for a raw look at some basic system communication. These were obtained by using mitmproxy as a websocket proxy and having the official Pentair Pool app connect to it while it was connected to the pool.
SubscribeToUpdates may be sent to be notified about changes to the given keys on the given object for the duration of the connection. It is unknown how/if you can unsubscribe after subscribing, but reconnecting to the unit will start fresh with no subscriptions registered.
Example:
unit.on("notify", (msg) => {
console.log("received notify:", msg);
});
await unit.send(messages.SubscribeToUpdates("B1202", ["LOTMP", "STATUS"]));
After calling the above function, any time the LOTMP or STATUS values change on body B1202, the notify
event will trigger with the new value(s).
Note: even if you subscribe to multiple keys on an object, any given notify
will only contain the actual key that changed. So always check if obj.params.LOTMP
, for example, is set before using it in a notify
callback.
Here is an implementation of SubscribeToUpdates
/ .on("notify")
that you can use as an example.
"close"
- fired when the search socket has closed"error"
- fired when an unrecoverable error has occurred in the search socket"serverFound"
- fired immediately when an IntelliCenter unit has been located; receives aUnitInfo
argument
Units will attempt to maintain a connection to the controller by pinging it every minute, but if the controller does not respond the connection will be terminated. Library consumers should handle the close event at a minimum and attempt to reconnect if the close was not intentional.
"response-{messageID}"
- fired once per message sent withsend()
where{messageID}
is the ID specified in theICRequest
given tosend()
"notify"
- fired when an update is available to a property previously subscribed to via aSubscribeToUpdates
request"close"
- fired any time the client is closed by any means (timeout, by request, error, etc.)"open"
- fired when the socket connects to the unit successfully"error"
- fired when the socket encounters an unrecoverable error and will close"timeout"
- fired when the socket has not received a ping response within the allowed threshold and will close"connected"
- fired when a connection has completed successfully