mirror of
https://github.com/parnic/node-intellicenter.git
synced 2025-06-17 02:21:53 -05:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
458048b58a
|
|||
829a90dd0e
|
|||
cc1a866233
|
|||
f8e2aa0f3e
|
|||
6c79e35e04
|
|||
331cea7007 |
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -9,8 +9,8 @@
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"program": "${workspaceFolder}/example.ts",
|
||||
"preLaunchTask": "tsc: build - tsconfig.json",
|
||||
"program": "${workspaceFolder}/src/example.ts",
|
||||
"preLaunchTask": "npm: build",
|
||||
"outFiles": ["${workspaceFolder}/**/*.js"],
|
||||
"env": {
|
||||
"DEBUG": "ic:*"
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v0.4.0] - 2025-01-25
|
||||
|
||||
### Changed
|
||||
|
||||
- Reinstated the websocket library being used. In some cases, such as MagicMirror modules, the WebSocket class is not available.
|
||||
|
||||
## [v0.3.0] - 2025-01-15
|
||||
|
||||
### Changed
|
||||
|
||||
- Dropped the websocket library being used in favor of the default one for node. This allows tests to set up a mock server that the unit can connect to. Theoretically there's no consumer-visible behavior difference here, but it is a major change under the hood.
|
||||
|
||||
## [v0.2.2] - 2025-01-08
|
||||
|
||||
### Changed
|
||||
|
12
README.md
12
README.md
@ -8,7 +8,7 @@ The [Wiki](https://github.com/parnic/node-intellicenter/wiki) has development no
|
||||
|
||||
## Usage
|
||||
|
||||
See example.ts for an example of interfacing with the library. Broadly, import the library with
|
||||
See [example.ts](./src/example.ts) for an example of interfacing with the library. Broadly, import the library with
|
||||
|
||||
```javascript
|
||||
// ESM
|
||||
@ -88,11 +88,11 @@ console.log(JSON.stringify(response, null, 2));
|
||||
|
||||
## Examples
|
||||
|
||||
See [the example script](./example.ts) (transpiled [CJS version](./cjs/example.js) | [ESM version](./esm/example.js)) for some code examples to get you going.
|
||||
See [the example script](./src/example.ts) (transpiled [CJS version](./cjs/example.js) | [ESM version](./esm/example.js)) for some code examples to get you going.
|
||||
|
||||
## List-objects script
|
||||
|
||||
There is a [list-objects](./list-objects.ts) script available for easily identifying objects on your controller that you can use to get info from or set attributes on.
|
||||
There is a [list-objects](./src/list-objects.ts) 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:
|
||||
|
||||
@ -113,7 +113,7 @@ Contributions are welcomed! Please open a pull request with your changes and let
|
||||
|
||||
## API reference
|
||||
|
||||
See the list of [messages](./messages/messages.ts) 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()](./messages/request.ts#L19) 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).
|
||||
See the list of [messages](./src/messages/messages.ts) 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()](./src/messages/request.ts#L19) 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.
|
||||
|
||||
@ -121,13 +121,13 @@ IntelliCenter controllers can handle lots of messages being thrown at them at on
|
||||
|
||||
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](./messages/request.ts) and [ICResponse](./messages/response.ts) as well as [ICParam](./messages/param.ts) for all known fields.
|
||||
See [ICRequest](./src/messages/request.ts) and [ICResponse](./src/messages/response.ts) as well as [ICParam](./src/messages/param.ts) for all known fields.
|
||||
|
||||
There are some redacted [connection logs](./connection-logs/) available for a raw look at some basic system communication. These were obtained by using [mitmproxy](https://mitmproxy.org/) as a websocket proxy and having the official Pentair Pool app connect to it while it was connected to the pool.
|
||||
|
||||
### Subscribing to updates
|
||||
|
||||
[SubscribeToUpdates](./messages/notify.ts) 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.
|
||||
[SubscribeToUpdates](./src/messages/notify.ts) 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:
|
||||
|
||||
|
2
cjs/unit.d.ts
vendored
2
cjs/unit.d.ts
vendored
@ -30,6 +30,8 @@ export declare class Unit extends EventEmitter {
|
||||
* Connects to the specified unit and maintains a connection to it until `close()` is called.
|
||||
*/
|
||||
connect(): Promise<void>;
|
||||
private onOpen;
|
||||
private onError;
|
||||
/**
|
||||
* Closes the connection to the unit.
|
||||
*/
|
||||
|
74
cjs/unit.js
74
cjs/unit.js
@ -1,12 +1,45 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Unit = void 0;
|
||||
const events_1 = require("events");
|
||||
const ws_1 = require("ws");
|
||||
const debug_1 = __importDefault(require("debug"));
|
||||
const ws = __importStar(require("ws"));
|
||||
const debugUnit = (0, debug_1.default)("ic:unit");
|
||||
/**
|
||||
* Contains methods to connect to and communicate with an IntelliCenter controller.
|
||||
@ -47,25 +80,16 @@ class Unit extends events_1.EventEmitter {
|
||||
throw new Error("can't open a client that is already open");
|
||||
}
|
||||
debugUnit(`connecting to ws://${this.endpoint}:${this.port.toString()}`);
|
||||
this.client = new ws_1.WebSocket(`ws://${this.endpoint}:${this.port.toString()}`);
|
||||
const { heartbeat, onClientMessage, socketCleanup } = this;
|
||||
this.client.on("error", (evt) => {
|
||||
// todo: emit event so we can reconnect? auto reconnect?
|
||||
debugUnit("error in websocket: $o", evt);
|
||||
this.emit("error", evt);
|
||||
socketCleanup();
|
||||
});
|
||||
this.client.on("open", () => {
|
||||
this.emit("open");
|
||||
heartbeat();
|
||||
});
|
||||
this.client.on("ping", heartbeat);
|
||||
this.client.on("pong", heartbeat);
|
||||
this.client = new ws.WebSocket(`ws://${this.endpoint}:${this.port.toString()}`);
|
||||
const { onOpen, onError, onClientMessage, socketCleanup } = this;
|
||||
this.client.on("error", onError);
|
||||
this.client.on("open", onOpen);
|
||||
this.client.on("close", socketCleanup);
|
||||
this.client.on("message", onClientMessage);
|
||||
this.pingTimer = setInterval(() => {
|
||||
debugUnit("sending ping");
|
||||
this.client?.ping();
|
||||
// this isn't an actual command that is recognized by the system, we just want to make sure they're still there.
|
||||
this.client?.send(JSON.stringify({ command: "ping" }));
|
||||
}, this.pingInterval);
|
||||
await new Promise((resolve, reject) => {
|
||||
this.client?.once("error", reject);
|
||||
@ -74,6 +98,16 @@ class Unit extends events_1.EventEmitter {
|
||||
debugUnit("connected");
|
||||
this.emit("connected");
|
||||
}
|
||||
onOpen = () => {
|
||||
this.emit("open");
|
||||
this.heartbeat();
|
||||
};
|
||||
onError = (evt) => {
|
||||
// todo: emit event so we can reconnect? auto reconnect?
|
||||
debugUnit("error in websocket: $o", evt);
|
||||
this.emit("error", evt);
|
||||
this.socketCleanup();
|
||||
};
|
||||
/**
|
||||
* Closes the connection to the unit.
|
||||
*/
|
||||
@ -104,12 +138,18 @@ class Unit extends events_1.EventEmitter {
|
||||
this.pingTimeout = setTimeout(() => {
|
||||
debugUnit("terminating connection due to heartbeat timeout");
|
||||
this.emit("timeout");
|
||||
this.client?.terminate();
|
||||
try {
|
||||
this.client?.close();
|
||||
}
|
||||
catch (ex) {
|
||||
debugUnit("exception trying to close client from ping timeout: %o", ex);
|
||||
}
|
||||
this.socketCleanup();
|
||||
}, this.pingInterval + 5000);
|
||||
};
|
||||
onClientMessage = (msg) => {
|
||||
debugUnit("message received, length %d", msg.length);
|
||||
this.heartbeat();
|
||||
const respObj = JSON.parse(msg.toString());
|
||||
if (respObj.command.toLowerCase() === "notifylist") {
|
||||
debugUnit(" it's a subscription confirmation or update");
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"unit.js","sourceRoot":"","sources":["../src/unit.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"unit.js","sourceRoot":"","sources":["../src/unit.ts"],"names":[],"mappings":"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"}
|
2
esm/unit.d.ts
vendored
2
esm/unit.d.ts
vendored
@ -30,6 +30,8 @@ export declare class Unit extends EventEmitter {
|
||||
* Connects to the specified unit and maintains a connection to it until `close()` is called.
|
||||
*/
|
||||
connect(): Promise<void>;
|
||||
private onOpen;
|
||||
private onError;
|
||||
/**
|
||||
* Closes the connection to the unit.
|
||||
*/
|
||||
|
41
esm/unit.js
41
esm/unit.js
@ -1,6 +1,6 @@
|
||||
import { EventEmitter } from "events";
|
||||
import { WebSocket } from "ws";
|
||||
import debug from "debug";
|
||||
import * as ws from "ws";
|
||||
const debugUnit = debug("ic:unit");
|
||||
/**
|
||||
* Contains methods to connect to and communicate with an IntelliCenter controller.
|
||||
@ -41,25 +41,16 @@ export class Unit extends EventEmitter {
|
||||
throw new Error("can't open a client that is already open");
|
||||
}
|
||||
debugUnit(`connecting to ws://${this.endpoint}:${this.port.toString()}`);
|
||||
this.client = new WebSocket(`ws://${this.endpoint}:${this.port.toString()}`);
|
||||
const { heartbeat, onClientMessage, socketCleanup } = this;
|
||||
this.client.on("error", (evt) => {
|
||||
// todo: emit event so we can reconnect? auto reconnect?
|
||||
debugUnit("error in websocket: $o", evt);
|
||||
this.emit("error", evt);
|
||||
socketCleanup();
|
||||
});
|
||||
this.client.on("open", () => {
|
||||
this.emit("open");
|
||||
heartbeat();
|
||||
});
|
||||
this.client.on("ping", heartbeat);
|
||||
this.client.on("pong", heartbeat);
|
||||
this.client = new ws.WebSocket(`ws://${this.endpoint}:${this.port.toString()}`);
|
||||
const { onOpen, onError, onClientMessage, socketCleanup } = this;
|
||||
this.client.on("error", onError);
|
||||
this.client.on("open", onOpen);
|
||||
this.client.on("close", socketCleanup);
|
||||
this.client.on("message", onClientMessage);
|
||||
this.pingTimer = setInterval(() => {
|
||||
debugUnit("sending ping");
|
||||
this.client?.ping();
|
||||
// this isn't an actual command that is recognized by the system, we just want to make sure they're still there.
|
||||
this.client?.send(JSON.stringify({ command: "ping" }));
|
||||
}, this.pingInterval);
|
||||
await new Promise((resolve, reject) => {
|
||||
this.client?.once("error", reject);
|
||||
@ -68,6 +59,16 @@ export class Unit extends EventEmitter {
|
||||
debugUnit("connected");
|
||||
this.emit("connected");
|
||||
}
|
||||
onOpen = () => {
|
||||
this.emit("open");
|
||||
this.heartbeat();
|
||||
};
|
||||
onError = (evt) => {
|
||||
// todo: emit event so we can reconnect? auto reconnect?
|
||||
debugUnit("error in websocket: $o", evt);
|
||||
this.emit("error", evt);
|
||||
this.socketCleanup();
|
||||
};
|
||||
/**
|
||||
* Closes the connection to the unit.
|
||||
*/
|
||||
@ -98,12 +99,18 @@ export class Unit extends EventEmitter {
|
||||
this.pingTimeout = setTimeout(() => {
|
||||
debugUnit("terminating connection due to heartbeat timeout");
|
||||
this.emit("timeout");
|
||||
this.client?.terminate();
|
||||
try {
|
||||
this.client?.close();
|
||||
}
|
||||
catch (ex) {
|
||||
debugUnit("exception trying to close client from ping timeout: %o", ex);
|
||||
}
|
||||
this.socketCleanup();
|
||||
}, this.pingInterval + 5000);
|
||||
};
|
||||
onClientMessage = (msg) => {
|
||||
debugUnit("message received, length %d", msg.length);
|
||||
this.heartbeat();
|
||||
const respObj = JSON.parse(msg.toString());
|
||||
if (respObj.command.toLowerCase() === "notifylist") {
|
||||
debugUnit(" it's a subscription confirmation or update");
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"unit.js","sourceRoot":"","sources":["../src/unit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;AAEnC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,IAAK,SAAQ,YAAY;IAO3B;IACA;IAPD,MAAM,CAAa;IACnB,WAAW,CAAiC;IAC5C,SAAS,CAAkC;IAC3C,YAAY,GAAG,KAAK,CAAC;IAE7B,YACS,QAAgB,EAChB,OAAO,IAAI;QAElB,KAAK,EAAE,CAAC;QAHD,aAAQ,GAAR,QAAQ,CAAQ;QAChB,SAAI,GAAJ,IAAI,CAAO;QAIlB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,OAAO;QAClB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QAED,SAAS,CAAC,sBAAsB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAEzE,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CACzB,QAAQ,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAChD,CAAC;QAEF,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC9B,wDAAwD;YACxD,SAAS,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACxB,aAAa,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YAC1B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClB,SAAS,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAE3C,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,SAAS,CAAC,cAAc,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAEtB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACnC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,SAAS,CAAC,WAAW,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACzB,CAAC;IAED;;OAEG;IACI,KAAK;QACV,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,SAAS,CAAC,+BAA+B,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAEO,aAAa,GAAG,GAAG,EAAE;QAC3B,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAE5B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QAExB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/B,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC/B,CAAC;QAED,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,CAAC;IACH,CAAC,CAAC;IAEM,SAAS,GAAG,GAAG,EAAE;QACvB,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAChC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAE/B,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,SAAS,CAAC,iDAAiD,CAAC,CAAC;YAC7D,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrB,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC,EAAE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEM,eAAe,GAAG,CAAC,GAAW,EAAE,EAAE;QACxC,SAAS,CAAC,6BAA6B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAErD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAe,CAAC;QACzD,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,YAAY,EAAE,CAAC;YACnD,SAAS,CAAC,8CAA8C,CAAC,CAAC;YAC1D,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC,CAAC;IAEF;;;;;OAKG;IACI,KAAK,CAAC,IAAI,CAAC,OAAkB;QAClC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE;gBAC5B,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;YAC1C,CAAC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,SAAS,CACP,yCAAyC,EACzC,OAAO,CAAC,MAAM,EACd,OAAO,CAAC,SAAS,CAClB,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YACnC,IAAI,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,IAAgB,EAAE,EAAE;gBAC9D,SAAS,CAAC,oCAAoC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;gBACnE,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
||||
{"version":3,"file":"unit.js","sourceRoot":"","sources":["../src/unit.ts"],"names":[],"mappings":"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"}
|
40
package-lock.json
generated
40
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "node-intellicenter",
|
||||
"version": "0.2.2",
|
||||
"version": "0.4.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "node-intellicenter",
|
||||
"version": "0.2.2",
|
||||
"version": "0.4.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
@ -19,9 +19,10 @@
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@types/ws": "^8.5.14",
|
||||
"eslint": "^9.17.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"markdownlint-cli2": "^0.17.1",
|
||||
"minimist": "^1.2.8",
|
||||
"ncp": "^2.0.0",
|
||||
@ -1518,9 +1519,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz",
|
||||
"integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -4059,6 +4060,17 @@
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-websocket-mock": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.5.0.tgz",
|
||||
"integrity": "sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jest-diff": "^29.2.0",
|
||||
"mock-socket": "^9.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
|
||||
@ -4173,9 +4185,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.19",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.19.tgz",
|
||||
"integrity": "sha512-3IA6DYVhxhBabjSLTNO9S4+OliA3Qvb8pBQXMfC4WxXJgLwZgnfDl0BmB4z6nBMdznBsZ+CGM8DrGZ5hcguDZg==",
|
||||
"version": "0.16.21",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz",
|
||||
"integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
@ -5009,6 +5021,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mock-socket": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz",
|
||||
"integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-intellicenter",
|
||||
"version": "0.2.2",
|
||||
"version": "0.4.0",
|
||||
"description": "NodeJS library for communicating with a Pentair IntelliCenter controller",
|
||||
"keywords": [
|
||||
"pentair",
|
||||
@ -90,9 +90,10 @@
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@types/ws": "^8.5.14",
|
||||
"eslint": "^9.17.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"markdownlint-cli2": "^0.17.1",
|
||||
"minimist": "^1.2.8",
|
||||
"ncp": "^2.0.0",
|
||||
|
44
src/unit.ts
44
src/unit.ts
@ -1,9 +1,9 @@
|
||||
import { EventEmitter } from "events";
|
||||
import { WebSocket } from "ws";
|
||||
import debug from "debug";
|
||||
|
||||
import { ICRequest } from "./messages/request.js";
|
||||
import { ICResponse } from "./messages/response.js";
|
||||
import * as ws from "ws";
|
||||
// needed for jsdoc
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import * as messages from "./messages/messages.js";
|
||||
@ -30,7 +30,7 @@ const debugUnit = debug("ic:unit");
|
||||
* * `"connected"` - fired when a connection has completed successfully
|
||||
*/
|
||||
export class Unit extends EventEmitter {
|
||||
private client?: WebSocket;
|
||||
private client?: ws.WebSocket;
|
||||
private pingTimeout?: ReturnType<typeof setTimeout>;
|
||||
private pingTimer?: ReturnType<typeof setInterval>;
|
||||
private pingInterval = 60000;
|
||||
@ -55,29 +55,20 @@ export class Unit extends EventEmitter {
|
||||
|
||||
debugUnit(`connecting to ws://${this.endpoint}:${this.port.toString()}`);
|
||||
|
||||
this.client = new WebSocket(
|
||||
this.client = new ws.WebSocket(
|
||||
`ws://${this.endpoint}:${this.port.toString()}`,
|
||||
);
|
||||
|
||||
const { heartbeat, onClientMessage, socketCleanup } = this;
|
||||
this.client.on("error", (evt) => {
|
||||
// todo: emit event so we can reconnect? auto reconnect?
|
||||
debugUnit("error in websocket: $o", evt);
|
||||
this.emit("error", evt);
|
||||
socketCleanup();
|
||||
});
|
||||
this.client.on("open", () => {
|
||||
this.emit("open");
|
||||
heartbeat();
|
||||
});
|
||||
this.client.on("ping", heartbeat);
|
||||
this.client.on("pong", heartbeat);
|
||||
const { onOpen, onError, onClientMessage, socketCleanup } = this;
|
||||
this.client.on("error", onError);
|
||||
this.client.on("open", onOpen);
|
||||
this.client.on("close", socketCleanup);
|
||||
this.client.on("message", onClientMessage);
|
||||
|
||||
this.pingTimer = setInterval(() => {
|
||||
debugUnit("sending ping");
|
||||
this.client?.ping();
|
||||
// this isn't an actual command that is recognized by the system, we just want to make sure they're still there.
|
||||
this.client?.send(JSON.stringify({ command: "ping" }));
|
||||
}, this.pingInterval);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
@ -89,6 +80,18 @@ export class Unit extends EventEmitter {
|
||||
this.emit("connected");
|
||||
}
|
||||
|
||||
private onOpen = () => {
|
||||
this.emit("open");
|
||||
this.heartbeat();
|
||||
};
|
||||
|
||||
private onError = (evt: ws.ErrorEvent) => {
|
||||
// todo: emit event so we can reconnect? auto reconnect?
|
||||
debugUnit("error in websocket: $o", evt);
|
||||
this.emit("error", evt);
|
||||
this.socketCleanup();
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection to the unit.
|
||||
*/
|
||||
@ -126,13 +129,18 @@ export class Unit extends EventEmitter {
|
||||
this.pingTimeout = setTimeout(() => {
|
||||
debugUnit("terminating connection due to heartbeat timeout");
|
||||
this.emit("timeout");
|
||||
this.client?.terminate();
|
||||
try {
|
||||
this.client?.close();
|
||||
} catch (ex) {
|
||||
debugUnit("exception trying to close client from ping timeout: %o", ex);
|
||||
}
|
||||
this.socketCleanup();
|
||||
}, this.pingInterval + 5000);
|
||||
};
|
||||
|
||||
private onClientMessage = (msg: Buffer) => {
|
||||
debugUnit("message received, length %d", msg.length);
|
||||
this.heartbeat();
|
||||
|
||||
const respObj = JSON.parse(msg.toString()) as ICResponse;
|
||||
if (respObj.command.toLowerCase() === "notifylist") {
|
||||
|
@ -1,3 +1,47 @@
|
||||
describe("empty test", () => {
|
||||
it("passes", () => {});
|
||||
import { Unit } from "../src/index.js";
|
||||
import * as messages from "../src/messages/messages.js";
|
||||
import * as WS from "jest-websocket-mock";
|
||||
import { xdescribe, beforeEach, afterEach, it } from "@jest/globals";
|
||||
|
||||
function makeid(length: number) {
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const charactersLength = characters.length;
|
||||
|
||||
let result = "";
|
||||
let counter = 0;
|
||||
while (counter < length) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// temporarily disabled: as long as Unit uses the "ws" library, it is incompatible with the jest-websocket-mock server
|
||||
xdescribe("basic message tests", () => {
|
||||
let unit: Unit;
|
||||
let mockServer: WS.WS;
|
||||
beforeEach(async () => {
|
||||
mockServer = new WS.WS("ws://localhost:6680");
|
||||
|
||||
unit = new Unit("localhost", 6680);
|
||||
await unit.connect();
|
||||
await mockServer.connected;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unit.close();
|
||||
WS.WS.clean();
|
||||
});
|
||||
|
||||
it("can send a message and return its response", async () => {
|
||||
const msg = messages.GetBodyStatus();
|
||||
const sender = unit.send(msg);
|
||||
await expect(mockServer).toReceiveMessage(JSON.stringify(msg));
|
||||
const response = { messageID: msg.messageID, command: makeid(8) };
|
||||
mockServer.send(JSON.stringify(response));
|
||||
const clientResp = await sender;
|
||||
expect(clientResp).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user