7 Commits

Author SHA1 Message Date
21d62549d3 Increase package version 2025-01-04 15:51:26 -06:00
9455ae224c Update vscode config 2025-01-04 15:51:19 -06:00
e8ebfe8e02 Add connected event
This enables event-based consumers to more easily react to connection completion.
2025-01-04 15:51:01 -06:00
3031bc2079 Update exports, rename example
This gives better control over importing in non-module environments and removes the example code as the default import which was running when loading up the library.
2025-01-04 15:50:24 -06:00
9e332038c6 Add ability to specify multicast interface
When you have multiple network adapters/interfaces, the system can choose the wrong one. This allows you to be explicit about which one you broadcast on.
2025-01-04 15:45:22 -06:00
033627d698 Add more docs, error handling 2025-01-04 13:15:08 -06:00
f59cc3438c Add connection events 2025-01-04 13:09:07 -06:00
18 changed files with 252 additions and 157 deletions

7
.vscode/launch.json vendored
View File

@ -9,9 +9,12 @@
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/index.ts",
"program": "${workspaceFolder}/example.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/**/*.js"]
"outFiles": ["${workspaceFolder}/**/*.js"],
"env": {
"DEBUG": "ic:*"
}
}
]
}

1
dist/example.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export {};

60
dist/example.js vendored Normal file
View File

@ -0,0 +1,60 @@
"use strict";
import { FindUnits, Unit } from "./index.js";
import { messages } from "./messages/messages.js";
console.log("searching...");
const f = new FindUnits("10.0.0.3");
const units = await f.searchAsync(1000);
f.close();
console.log("Discovered units:", units);
if (units.length === 0) {
throw new Error("no IntelliCenter units found, exiting.");
}
if (units.length > 1) {
throw new Error(`found more than one IntelliCenter unit, unsure which one to use. ${JSON.stringify(units)}`);
}
const endpoint = units[0].addressStr;
const port = units[0].port;
// const endpoint = "10.0.0.41";
// const port = 6680;
console.log("connecting to intellicenter device at", endpoint, "port", port);
const unit = new Unit(endpoint, port);
await unit.connect();
console.log("connected");
unit.on("notify", (msg) => {
console.log("received notify:", msg);
});
console.log("subscribing for updates...");
let resp = await unit.send(messages.SubscribeToUpdates("B1202", "LOTMP"));
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Info request...");
resp = await unit.send(messages.GetSystemInformation());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Config request...");
resp = await unit.send(messages.GetSystemConfiguration());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Body Status request...");
resp = await unit.send(messages.GetBodyStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Chemical Status request...");
resp = await unit.send(messages.GetChemicalStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Heaters request...");
resp = await unit.send(messages.GetHeaters());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Schedule request...");
resp = await unit.send(messages.GetSchedule());
console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Setpoint request...");
// resp = await unit.send(messages.SetSetpoint("B1202", 97));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off pool...");
// resp = await unit.send(messages.SetItemStatus("B1101", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off water feature...");
// resp = await unit.send(messages.SetItemStatus("C0003", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Heatmode request...");
// resp = await unit.send(messages.SetHeatMode("B1202", true));
// console.log("got response:", JSON.stringify(resp, null, 2));
unit.close();
//# sourceMappingURL=example.js.map

1
dist/example.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"example.js","sourceRoot":"","sources":["../example.ts"],"names":[],"mappings":"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"}

8
dist/finder.d.ts vendored
View File

@ -20,7 +20,13 @@ export declare class UnitInfo {
* * `"serverFound"` - fired immediately when an IntelliCenter unit has been located; receives a {@linkcode UnitInfo} argument
*/
export declare class FindUnits extends EventEmitter {
constructor();
broadcastInterface?: string | undefined;
/**
* Creates a new finder.
*
* @param broadcastInterface the address of the interface to send the broadcast to. If not specified, will use system selection. Only necessary if you have more than one network adapter/interface and want to search on a specific one.
*/
constructor(broadcastInterface?: string | undefined);
private finder;
private bound;
private message;

12
dist/finder.js vendored
View File

@ -31,8 +31,15 @@ export class UnitInfo {
* * `"serverFound"` - fired immediately when an IntelliCenter unit has been located; receives a {@linkcode UnitInfo} argument
*/
export class FindUnits extends EventEmitter {
constructor() {
broadcastInterface;
/**
* Creates a new finder.
*
* @param broadcastInterface the address of the interface to send the broadcast to. If not specified, will use system selection. Only necessary if you have more than one network adapter/interface and want to search on a specific one.
*/
constructor(broadcastInterface) {
super();
this.broadcastInterface = broadcastInterface;
// construct mDNS packet to ping for intellicenter controllers
this.message = Buffer.alloc(34);
let offset = 0;
@ -54,6 +61,9 @@ export class FindUnits extends EventEmitter {
this.finder = createSocket("udp4");
this.finder
.on("listening", () => {
if (this.broadcastInterface) {
this.finder.setMulticastInterface(this.broadcastInterface);
}
this.finder.setBroadcast(true);
this.finder.setMulticastTTL(128);
if (!this.bound) {

2
dist/finder.js.map vendored

File diff suppressed because one or more lines are too long

4
dist/index.d.ts vendored
View File

@ -1 +1,3 @@
export {};
import { FindUnits } from "./finder.js";
import { Unit } from "./unit.js";
export { FindUnits, Unit };

59
dist/index.js vendored
View File

@ -1,61 +1,4 @@
"use strict";
import { FindUnits } from "./finder.js";
import { messages } from "./messages/messages.js";
import { Unit } from "./unit.js";
console.log("searching...");
const f = new FindUnits();
const units = await f.searchAsync(1000);
f.close();
console.log("Discovered units:", units);
if (units.length === 0) {
throw new Error("no IntelliCenter units found, exiting.");
}
if (units.length > 1) {
throw new Error(`found more than one IntelliCenter unit, unsure which one to use. ${JSON.stringify(units)}`);
}
const endpoint = units[0].addressStr;
const port = units[0].port;
// const endpoint = "10.0.0.41";
// const port = 6680;
console.log("connecting to intellicenter device at", endpoint, "port", port);
const unit = new Unit(endpoint, port);
await unit.connect();
console.log("connected");
unit.on("notify", (msg) => {
console.log("received notify:", msg);
});
console.log("subscribing for updates...");
let resp = await unit.send(messages.SubscribeToUpdates("B1202", "LOTMP"));
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Info request...");
resp = await unit.send(messages.GetSystemInformation());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Config request...");
resp = await unit.send(messages.GetSystemConfiguration());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Body Status request...");
resp = await unit.send(messages.GetBodyStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Chemical Status request...");
resp = await unit.send(messages.GetChemicalStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Heaters request...");
resp = await unit.send(messages.GetHeaters());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Schedule request...");
resp = await unit.send(messages.GetSchedule());
console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Setpoint request...");
// resp = await unit.send(messages.SetSetpoint("B1202", 97));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off pool...");
// resp = await unit.send(messages.SetItemStatus("B1101", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off water feature...");
// resp = await unit.send(messages.SetItemStatus("C0003", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Heatmode request...");
// resp = await unit.send(messages.SetHeatMode("B1202", true));
// console.log("got response:", JSON.stringify(resp, null, 2));
unit.close();
export { FindUnits, Unit };
//# sourceMappingURL=index.js.map

2
dist/index.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC"}

7
dist/unit.d.ts vendored
View File

@ -5,11 +5,18 @@ import { ICResponse } from "./messages/response.js";
* Contains methods to connect to and communicate with an IntelliCenter controller.
*
* Call `connect` to connect to the unit.
* Use `send` to send a message.
* Subscribe to events to process socket conditions, notify updates, and message responses (if not `await`ing the response)
*
* Available events:
*
* * `"response-{messageID}"` - fired once per message sent with `send()` where {messageID} is the ID specified in the {@linkcode ICRequest} given to `send()`
* * `"notify"` - fired when an update is available to a property previously subscribed to via a {@linkcode SubscribeToUpdates} 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
*/
export declare class Unit extends EventEmitter {
endpoint: string;

28
dist/unit.js vendored
View File

@ -6,11 +6,18 @@ const debugUnit = debug("ic:unit");
* Contains methods to connect to and communicate with an IntelliCenter controller.
*
* Call `connect` to connect to the unit.
* Use `send` to send a message.
* Subscribe to events to process socket conditions, notify updates, and message responses (if not `await`ing the response)
*
* Available events:
*
* * `"response-{messageID}"` - fired once per message sent with `send()` where {messageID} is the ID specified in the {@linkcode ICRequest} given to `send()`
* * `"notify"` - fired when an update is available to a property previously subscribed to via a {@linkcode SubscribeToUpdates} 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
*/
export class Unit extends EventEmitter {
endpoint;
@ -39,9 +46,13 @@ export class Unit extends EventEmitter {
this.client.on("error", (evt) => {
// todo: emit event so we can reconnect? auto reconnect?
debugUnit("error in websocket: $o", evt);
this.emit("error");
socketCleanup();
});
this.client.on("open", heartbeat);
this.client.on("open", () => {
this.emit("open");
heartbeat();
});
this.client.on("ping", heartbeat);
this.client.on("pong", heartbeat);
this.client.on("close", socketCleanup);
@ -55,13 +66,18 @@ export class Unit extends EventEmitter {
this.client?.once("open", resolve);
});
debugUnit("connected");
this.emit("connected");
}
/**
* Closes the connection to the unit.
*/
close() {
if (!this.client) {
return;
}
debugUnit("closing connection by request");
this.client?.close();
this.emit("close");
this.client.close();
}
socketCleanup = () => {
debugUnit("socket cleanup");
@ -81,6 +97,7 @@ export class Unit extends EventEmitter {
clearTimeout(this.pingTimeout);
this.pingTimeout = setTimeout(() => {
debugUnit("terminating connection due to heartbeat timeout");
this.emit("timeout");
this.client?.terminate();
this.socketCleanup();
}, this.pingInterval + 5000);
@ -101,9 +118,14 @@ export class Unit extends EventEmitter {
* @returns a promise that resolves into the {@linkcode ICResponse} with information about the request.
*/
async send(request) {
if (!this.client) {
return await new Promise(() => {
throw new Error("client not connected");
});
}
const payload = JSON.stringify(request);
debugUnit("sending message of length %d with id %s", payload.length, request.messageID);
this.client?.send(payload);
this.client.send(payload);
return await new Promise((resolve) => {
this.once(`response-${request.messageID}`, (resp) => {
debugUnit(" returning response to message %s", request.messageID);

2
dist/unit.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"unit.js","sourceRoot":"","sources":["../unit.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"unit.js","sourceRoot":"","sources":["../unit.ts"],"names":[],"mappings":"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"}

81
example.ts Normal file
View File

@ -0,0 +1,81 @@
"use strict";
import { FindUnits, Unit } from "./index.js";
import { messages } from "./messages/messages.js";
console.log("searching...");
const f = new FindUnits("10.0.0.3");
const units = await f.searchAsync(1000);
f.close();
console.log("Discovered units:", units);
if (units.length === 0) {
throw new Error("no IntelliCenter units found, exiting.");
}
if (units.length > 1) {
throw new Error(
`found more than one IntelliCenter unit, unsure which one to use. ${JSON.stringify(units)}`,
);
}
const endpoint = units[0].addressStr;
const port = units[0].port;
// const endpoint = "10.0.0.41";
// const port = 6680;
console.log("connecting to intellicenter device at", endpoint, "port", port);
const unit = new Unit(endpoint, port);
await unit.connect();
console.log("connected");
unit.on("notify", (msg) => {
console.log("received notify:", msg);
});
console.log("subscribing for updates...");
let resp = await unit.send(messages.SubscribeToUpdates("B1202", "LOTMP"));
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Info request...");
resp = await unit.send(messages.GetSystemInformation());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Config request...");
resp = await unit.send(messages.GetSystemConfiguration());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Body Status request...");
resp = await unit.send(messages.GetBodyStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Chemical Status request...");
resp = await unit.send(messages.GetChemicalStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Heaters request...");
resp = await unit.send(messages.GetHeaters());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Schedule request...");
resp = await unit.send(messages.GetSchedule());
console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Setpoint request...");
// resp = await unit.send(messages.SetSetpoint("B1202", 97));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off pool...");
// resp = await unit.send(messages.SetItemStatus("B1101", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off water feature...");
// resp = await unit.send(messages.SetItemStatus("C0003", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Heatmode request...");
// resp = await unit.send(messages.SetHeatMode("B1202", true));
// console.log("got response:", JSON.stringify(resp, null, 2));
unit.close();

View File

@ -51,7 +51,12 @@ export class UnitInfo {
* * `"serverFound"` - fired immediately when an IntelliCenter unit has been located; receives a {@linkcode UnitInfo} argument
*/
export class FindUnits extends EventEmitter {
constructor() {
/**
* Creates a new finder.
*
* @param broadcastInterface the address of the interface to send the broadcast to. If not specified, will use system selection. Only necessary if you have more than one network adapter/interface and want to search on a specific one.
*/
constructor(public broadcastInterface?: string) {
super();
// construct mDNS packet to ping for intellicenter controllers
@ -76,6 +81,9 @@ export class FindUnits extends EventEmitter {
this.finder = createSocket("udp4");
this.finder
.on("listening", () => {
if (this.broadcastInterface) {
this.finder.setMulticastInterface(this.broadcastInterface);
}
this.finder.setBroadcast(true);
this.finder.setMulticastTTL(128);

View File

@ -1,82 +1,4 @@
"use strict";
import { FindUnits } from "./finder.js";
import { messages } from "./messages/messages.js";
import { Unit } from "./unit.js";
console.log("searching...");
const f = new FindUnits();
const units = await f.searchAsync(1000);
f.close();
console.log("Discovered units:", units);
if (units.length === 0) {
throw new Error("no IntelliCenter units found, exiting.");
}
if (units.length > 1) {
throw new Error(
`found more than one IntelliCenter unit, unsure which one to use. ${JSON.stringify(units)}`,
);
}
const endpoint = units[0].addressStr;
const port = units[0].port;
// const endpoint = "10.0.0.41";
// const port = 6680;
console.log("connecting to intellicenter device at", endpoint, "port", port);
const unit = new Unit(endpoint, port);
await unit.connect();
console.log("connected");
unit.on("notify", (msg) => {
console.log("received notify:", msg);
});
console.log("subscribing for updates...");
let resp = await unit.send(messages.SubscribeToUpdates("B1202", "LOTMP"));
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Info request...");
resp = await unit.send(messages.GetSystemInformation());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get System Config request...");
resp = await unit.send(messages.GetSystemConfiguration());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Body Status request...");
resp = await unit.send(messages.GetBodyStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Chemical Status request...");
resp = await unit.send(messages.GetChemicalStatus());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Heaters request...");
resp = await unit.send(messages.GetHeaters());
console.log("got response:", JSON.stringify(resp, null, 2));
console.log("sending Get Schedule request...");
resp = await unit.send(messages.GetSchedule());
console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Setpoint request...");
// resp = await unit.send(messages.SetSetpoint("B1202", 97));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off pool...");
// resp = await unit.send(messages.SetItemStatus("B1101", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("turning off water feature...");
// resp = await unit.send(messages.SetItemStatus("C0003", false));
// console.log("got response:", JSON.stringify(resp, null, 2));
// console.log("sending Set Heatmode request...");
// resp = await unit.send(messages.SetHeatMode("B1202", true));
// console.log("got response:", JSON.stringify(resp, null, 2));
unit.close();
export { FindUnits, Unit };

View File

@ -1,6 +1,6 @@
{
"name": "node-intellicenter",
"version": "0.0.1",
"version": "0.0.2",
"description": "NodeJS library for communicating with a Pentair IntelliCenter controller",
"keywords": [
"pentair",
@ -21,13 +21,18 @@
"license": "MIT",
"author": "Parnic",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./unit": "./dist/unit.js",
"./finder": "./dist/finder.js",
"./messages": "./dist/messages/messages.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"index": "tsc && node dist/index.js",
"lint": "eslint . && prettier . --check"
"example": "tsc && node dist/example.js",
"lint": "eslint . && prettier . --check",
"lint:fix": "eslint . --fix && prettier . --write"
},
"dependencies": {
"debug": "^4.4.0",

30
unit.ts
View File

@ -16,11 +16,18 @@ const debugUnit = debug("ic:unit");
* Contains methods to connect to and communicate with an IntelliCenter controller.
*
* Call `connect` to connect to the unit.
* Use `send` to send a message.
* Subscribe to events to process socket conditions, notify updates, and message responses (if not `await`ing the response)
*
* Available events:
*
* * `"response-{messageID}"` - fired once per message sent with `send()` where {messageID} is the ID specified in the {@linkcode ICRequest} given to `send()`
* * `"notify"` - fired when an update is available to a property previously subscribed to via a {@linkcode SubscribeToUpdates} 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
*/
export class Unit extends EventEmitter {
private client?: WebSocket;
@ -56,9 +63,13 @@ export class Unit extends EventEmitter {
this.client.on("error", (evt) => {
// todo: emit event so we can reconnect? auto reconnect?
debugUnit("error in websocket: $o", evt);
this.emit("error");
socketCleanup();
});
this.client.on("open", heartbeat);
this.client.on("open", () => {
this.emit("open");
heartbeat();
});
this.client.on("ping", heartbeat);
this.client.on("pong", heartbeat);
this.client.on("close", socketCleanup);
@ -75,14 +86,20 @@ export class Unit extends EventEmitter {
});
debugUnit("connected");
this.emit("connected");
}
/**
* Closes the connection to the unit.
*/
public close() {
if (!this.client) {
return;
}
debugUnit("closing connection by request");
this.client?.close();
this.emit("close");
this.client.close();
}
private socketCleanup = () => {
@ -108,6 +125,7 @@ export class Unit extends EventEmitter {
this.pingTimeout = setTimeout(() => {
debugUnit("terminating connection due to heartbeat timeout");
this.emit("timeout");
this.client?.terminate();
this.socketCleanup();
}, this.pingInterval + 5000);
@ -132,6 +150,12 @@ export class Unit extends EventEmitter {
* @returns a promise that resolves into the {@linkcode ICResponse} with information about the request.
*/
public async send(request: ICRequest): Promise<ICResponse> {
if (!this.client) {
return await new Promise(() => {
throw new Error("client not connected");
});
}
const payload = JSON.stringify(request);
debugUnit(
"sending message of length %d with id %s",
@ -139,7 +163,7 @@ export class Unit extends EventEmitter {
request.messageID,
);
this.client?.send(payload);
this.client.send(payload);
return await new Promise((resolve) => {
this.once(`response-${request.messageID}`, (resp: ICResponse) => {
debugUnit(" returning response to message %s", request.messageID);