116 Commits

Author SHA1 Message Date
b2faf57d09 Prepare for 1.8.0 release 2022-04-17 10:47:56 -05:00
c95c9f8ce5 Fix day-of-week handling again
Saturday isn't 0, it's 7.

Add tests to make sure this is working now and forever!
2022-04-17 10:47:45 -05:00
79bff51c17 Satisfy linter 2022-04-15 16:38:29 -05:00
485b494ea8 Add support for reading unit history data
unit.getHistoryData(fromTime, toTime) will request data about sensor and circuit changes over time, then `getHistoryData` will be emitted when the data is ready to be handled.

Fixes #59
2022-04-15 16:38:24 -05:00
4de8a63140 Fix readme typo 2022-04-15 16:24:33 -05:00
e0136a01cd Fix SLDateTime day-of-week calculation
Turns out we were offsetting the wrong direction. This offsets the correct way (verified against a Wireshark capture of the official app).
2022-04-15 16:17:18 -05:00
de8bba63a0 Alphabetize most of the readme sections
And indent the individual messages a bit to help the layout presentation.
2022-04-15 16:13:50 -05:00
77b034ce57 Add reading of SLTimes 2022-04-15 14:55:26 -05:00
0db9e4945e Run npm update 2022-02-01 09:35:32 -06:00
ada16cd355 Run npm audit fix 2022-02-01 09:33:38 -06:00
cbe64bbb2e Update dependencies 2022-01-07 14:34:40 -06:00
4cfffdd9df Drop support for nodejs 10.x, add 14.x and 16.x
Update package.json version
2021-10-13 14:14:41 -05:00
ba6dac4399 Update changelog, dependencies 2021-10-13 14:12:05 -05:00
f406bccb5d Update coolSetPoint documentation per user findings
Closes #57
2021-09-06 23:09:44 -05:00
a5d207d3aa Add support for setting the current system date/time
#56
2021-09-06 23:02:02 -05:00
f271554d89 Add DateTime writing support to SLMessages
There may be some value in providing a read version of this as well, but
it is comprised of so many properties that I'm leaving that out for now.
If it becomes a need in the future, it will be straightforward to add.
2021-09-06 23:01:42 -05:00
37d40b3386 Add support for retrieving the current system date/time
#56
2021-09-06 23:00:51 -05:00
6f1ee3c13f Add info about add/remove client function events 2021-09-06 22:56:16 -05:00
0b990bbc28 Add links to time conversion functions 2021-09-06 21:51:50 -05:00
2aa14cc114 Fix SLGetScheduleData documentation 2021-09-06 21:51:50 -05:00
bef8e6a379 Bump glob-parent from 5.1.1 to 5.1.2
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 07:23:37 -05:00
5bbbfb6f41 Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-10 22:26:51 -05:00
fa43b3c03e Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 14:36:05 -05:00
789f75f4a6 Add missing break 2020-12-12 21:14:41 -06:00
1ea8548991 Add handling/documentation for the scheduleChanged event
See discussion on #44 for more info
2020-12-12 21:12:15 -06:00
542d2f3e94 Disabled scheduled CodeQL runs 2020-11-28 19:44:23 -06:00
5f5b52f1ab Tag new version, update dev dependencies 2020-11-23 08:15:44 -06:00
7f76cb2a54 Update changelog for release 2020-11-23 08:13:43 -06:00
863cd7b1e6 Create codeql-analysis.yml 2020-10-01 11:57:43 -05:00
a53909eaa2 Add table of contents to readme 2020-08-22 21:33:24 -05:00
27bdf0380e Ensure all messages populate the response senderId
Fixed `addClient` and `removeClient` events returning `SLCancelDelay` objects. This shouldn't really make a difference as there are no properties to worry about on one or the other, but it was still incorrect and could cause bugs if stuff was added to those messages in the future.
2020-08-12 08:51:44 -05:00
8294947f8c Update dev dependencies due to vulnerabilities 2020-08-11 22:31:18 -05:00
b0225c69bd Update to point at the new default branch name 2020-08-11 22:28:07 -05:00
89b8775ce3 Add support for specifying the sender id to each call
This parameter is optional, so compatibility shouldn't be affected. Each SLMessage can set its own sender ID which is present on the returned message. This allows callers to fire multiple requests at once, even of the same type, while being able to identify which response went with which request. If not specified, the default value is 0.

Also went ahead and documented some of the helper functions present on SLMessage (so, available on all message instances).

Finally, since I was in and messing with each message anyway, I simplified and removed some repeated code from each derived message and had it call into the super to take advantage of shared decoding functionality.

The lambdas ("arrow functions") in test functions were removed per advice from Mocha's documentation where the implicit `this` rebinding can apparently cause problems. This should probably have been its own commit, but, again, I was already in there messing with stuff, so...oh well.

Closes #43
2020-08-11 22:20:58 -05:00
ab36d17a38 Increase version for NPM-specific fix
I had local changes to the example script that inadvertently were pushed to npm as part of 1.6.0, so that release on npm didn't match the repo's contents.
2020-07-15 20:58:41 -05:00
c36e4cbaa2 Fix changelog release date
Time is meaningless these days anyway...
2020-07-14 15:55:34 -05:00
df22901ea1 Add another missed attribution 2020-07-14 12:51:05 -05:00
b23f488822 Remove specific node versions tested against
These change frequently and it's probably not worth documenting anyway. I'm pretty sure these versions were no longer valid anyway since, for example, Mocha no longer supports node v8.
2020-07-14 12:50:50 -05:00
d7d10d7408 Update outdated dependencies, fix npm audit 2020-07-14 12:47:21 -05:00
b80807448d Update changelog/package.json for release 2020-07-14 12:46:43 -05:00
2e5203fb85 Adds keepalive setting to socket 2020-06-28 15:40:14 -05:00
0a0c8c7cd0 Bug Fix for decodeValveData / improvedNaming / CIRCUIT_VALUE_MAP (#33)
* initial code to interpret valveDataArray

* Some additional comments on the valveDataArray decoding message

* Adds decodeSensorData, decodeValveData, decodeDelayData, decodeMiscData

* reverts unintended change to index.js

* Refactor / rename variables
	- renamed variables to better describe their use
	- created a helper function 'isValvePresent' to simplify code
	- changed while loop to for loop

* - Fixes bug in decodeValveData where only last valve's data was returned
- renames some variables based on testing to more descriptive names
- add CIRCUIT_NAME_VALUE_MAP for fixed values that the system uses (determined by testing on my system)

* refactored decodeMiscData to simplify code

* - Changed CIRCUIT_NAME_VALUE_MAP to array of objects
- Refactored getCircuitByDeviceId
- Added getCircuitsMap as helper to getCircuitByDeviceId and as a utility function to help in UI development
2020-06-19 09:57:51 -05:00
1e13036faf decodeSensorData, decodeValveData, decodeDelayData, decodeMiscData (#32)
* initial code to interpret valveDataArray

* Some additional comments on the valveDataArray decoding message

* Adds decodeSensorData, decodeValveData, decodeDelayData, decodeMiscData

* reverts unintended change to index.js

* Refactor / rename variables
	- renamed variables to better describe their use
	- created a helper function 'isValvePresent' to simplify code
	- changed while loop to for loop
2020-06-18 16:22:58 -05:00
addd89bec5 Day mask helper functions / Refactor of time functions (#31)
* added helper methods for decode/encode dayMask

* moved isBitSet to SLMessage.js as this method is needed for several message types

* Refactored reusable functions to SLMessage
2020-06-16 21:22:22 -05:00
1aea09d95e Missed these new ones 2020-06-12 13:44:09 -05:00
9e158ae903 Made readme and changelog markdownlint compliant 2020-06-12 13:42:25 -05:00
b2439cd90c Added SLAddClient / SLRemoveClient messages (#30) 2020-06-10 08:05:32 -05:00
71c606cb94 Updated changelog 2020-06-09 16:52:36 -05:00
98f759c717 Added debug logging, updated readme with new message+event 2020-06-09 16:51:41 -05:00
407a4ec1f2 Added attribution to changelog items 2020-06-09 16:46:01 -05:00
db522ba5db SLCancelDelay message added (#29) 2020-06-09 16:43:09 -05:00
626ca5b64f Added recent work to the changelog 2020-06-09 09:31:14 -05:00
51d5b5a620 Added dependency on debug package, converted console.log to debug logs
I find these console logs valuable for development but don't want them on all the time for everyone. It's cumbersome to comment/uncomment them continually, so this package helps nicely. I generally don't like adding dependencies, but this one seems wildly popular and itself only has one dependency (and that dependencies doesn't depend on anything else). So it seems acceptable to use.

I also enabled these debug logs by default when running the example configuration from VSCode.
2020-06-09 09:25:40 -05:00
71dad9a51b Disabled max-len eslint rule for the entire project
Personally I've never found value in keeping line lengths low and it just leads to code that's awkward to write and read. No sense in disabling this per-file.
2020-06-09 09:22:25 -05:00
c0ac043d7b Added constants for heat modes 2020-06-09 08:47:32 -05:00
ee30cd815e GetPumpStatus isRPMs changed to bool to be consistent with SetPumpStatus 2020-06-09 08:42:26 -05:00
bf3902efe8 Updated formatting/semantics, added missing event/method documentation 2020-06-09 08:41:15 -05:00
e6acd36562 Added helper to get the number of pumps installed in a system 2020-06-09 08:39:38 -05:00
75d642cbfc GetPumpStatus / SetPumpFlow commands (#28)
* added GetPumpStatus / SetPumpFlow commands

* Updated README.md with GetPumpStatus / SetPumpFlow information

* rename `setPointType` to `isRPMs`

* renamed val0 to pumpType and val1 to isRunning

* added `unknown command` case

* fixed lint error

* Renamed unknown values to pumpUnknown1 and pumpUnknown2

* renamed 30 to unknownCommand
2020-06-09 07:49:24 -05:00
219ce110ea Added an extra GitHub action to run SLMessage tests 2020-06-08 22:21:06 -05:00
cd981c19ef Added ability to get circuit info by the device id
This is useful for EquipmentConfiguration and upcoming PumpStatus messages.
2020-06-08 22:14:31 -05:00
8d908aad5d WIP interpretation of equipment configuration properties 2020-06-08 19:05:45 -05:00
6013410525 Ran npm install 2020-06-06 19:04:00 -05:00
95140d112e Preparing for release 2020-06-06 19:02:29 -05:00
db28ef4bbc Updated outdated dependencies 2020-06-06 18:59:39 -05:00
c1ceacbd98 Updated Readme / Added EggTimer Support (#25)
* added setCircuitRuntimeById function
2020-06-06 18:57:58 -05:00
9c72e7b61d Schedule Editing Additions (#24)
* Added support for: adding schedule events, deleting schedule events, listing schedule events and updating scheduled events
2020-06-03 22:07:33 -05:00
def2d8aad4 Fixed typo in github action script 2020-05-25 14:09:56 -05:00
2d6d694aa5 Create nodejs.yml 2020-05-25 14:06:19 -05:00
a59fae5f8c Updated dependency to latest
Preparing for v1.4.0 release
2020-05-25 13:41:13 -05:00
f315bcd6a5 Allowed eslint to fix style violations 2020-05-25 13:25:18 -05:00
a0e2569743 Updated readme with new events 2020-05-25 13:25:01 -05:00
9205e3b62b add .on(‘error’) handlers that emit the error (#21)
- propagate internal errors back to caller by emitting them so they can properly be handled by callers
2020-05-25 13:19:28 -05:00
8e0cb20620 Added missing events and corrected heat mode event 2020-04-02 13:29:43 -05:00
98edd602ec Updated to support non-Windows platforms 2020-03-29 10:54:06 -05:00
ba1fb3fb6c Updated dependencies to fix vulnerabilities 2020-03-28 16:32:08 -05:00
6a1d31c224 Made eslint happy again 2020-03-04 21:58:13 -06:00
43725ae083 Fixed remote login
I forgot that this message sometimes receives a string instead of a buffer.

Fixes #19
2020-03-04 21:56:06 -06:00
81b3a61c28 Added SLEquipmentConfigurationMessage
I am not documenting this in the readme just yet because I don't really know how to interpret the data provided, but it seems to be necessary if we want to be able to change pump speeds in the future or read any historical data about the equipment. I'm not sure how much I'm going to be able to figure out about these arrays since the data appears random to me at the moment, but perhaps others will be able to figure out what's in here.
2020-02-22 11:29:29 -06:00
da9864462b Renamed to Example for user-friendliness 2020-02-22 11:27:30 -06:00
a2d39e7463 Added some helpers to interpret the controllerType field
These aren't particularly useful, at least not to me, but I found them while perusing the official app, so I figured they couldn't hurt to have.
2020-02-22 11:27:09 -06:00
ed99d411b2 Minor optimization to set buffer size appropriately
This avoids unnecessary allocations/reallocations while decoding a message by pre-sizing the buffer to the amount we know it will require. I feel like there's probably a better way to handle this, but this works for now.
2020-02-10 21:36:13 -06:00
5a55c56ac1 I'm reading that this is supposed to be submitted to source control 2020-02-09 15:29:17 -06:00
e24f49285c Added tests for some of the SLMessage utilities 2020-02-09 14:51:23 -06:00
ba19b6802e Added support for reading arrays from screenlogic messages 2020-02-09 14:51:10 -06:00
c9afb53810 SLMessage now supports instancing from an encoded SLMessage
This allows the base class to be evaluated by unit tests in a way that mocks what the library does with actual messages received from pool equipment.
2020-02-09 14:39:43 -06:00
22357f11e2 Encapsulate alignment into a function to make things slightly easier
This just returns the extra amount needed to add onto the string/array.

Also fixed up writeSLArray() not adding slack like it should have been. This wasn't an issue previously because the only place that currently uses this function is the password feature which is a fixed 16byte length, so it was already aligned. An upcoming commit will be using this, however, so I needed the alignment to be correct.

Finally, the read/write string functions were doing unnecessary conditional checks so I removed them. skipWrite() already does nothing if you tell it to skip 0, and adding 0 to readOffset won't do anything, so neither conditional is necessary at this point.
2020-02-09 14:28:03 -06:00
22858061f7 eslint didn't like these not having semi-colons
I should probably turn the semi-colon linter off since I really dislike the semi-colons everywhere, but it's probably too late for that at this point in the project.
2020-02-09 14:22:32 -06:00
9337068826 Don't change any equipment state when running tests
This is handy for testing certain mutators, but shouldn't happen every time we run tests. It can be enabled by the user on a case-by-case basis.
2020-02-09 14:21:54 -06:00
0397e8ad8f Added support for controlling the salt cell generator output levels
Closes #17
2020-02-08 15:33:13 -06:00
c310885598 Updated changelog and package.json for release 2019-12-27 15:57:36 -06:00
661d8db173 Added helper methods for interpreting the equipFlags value
Provided by @mikemucc in #15. Also updated the readme with the new information.
2019-12-27 15:57:21 -06:00
e567e9c821 gatewayName conversion was stripping final character (#14) 2019-11-30 12:01:29 -06:00
b8ed58e070 Updated changelog and package.json for release 2019-11-26 17:41:26 -06:00
757a3be7d1 Set heat mode (#13)
* Function to set the heater mode + readme updates

* making the test set the heater to 'heat pump'
2019-11-24 16:53:53 -06:00
1dcbe3883b Updated readme with latest Pentair equipment firmware version 2019-07-30 12:09:31 -05:00
b95f6ff777 Fixed spelling of Caribbean
I knew that first one didn't look right, but it's what the official app's source code had, so I went with it.
2019-07-29 21:48:20 -05:00
5ec3fbb802 Added ability to send light commands (on, off, set colors, etc.)
This implementation is barebones and only does what I was able to find easily in the official app's source code. I'd love to do more with this, so any pull requests adding functionality would be welcomed.

Closes https://github.com/parnic/node-screenlogic/issues/4
2019-07-29 21:37:36 -05:00
3b774923eb Updated changelog 2019-06-22 10:12:04 -05:00
64edc5879b Added handling of the system disliking function arguments
Updated readme with failure events that are triggered
2019-06-22 10:10:13 -05:00
cf3d3cba78 Fixed eslint complaint from commit 3824437 2019-06-21 00:13:11 -05:00
1aa1dddb49 Added ability to set pool or spa heat setpoint
Fixes #9
2019-06-21 00:12:29 -05:00
3824437d7a Factor out circuitData() and use it for is{Spa,Pool}Active (#8) 2019-06-14 16:53:42 -05:00
cd2354c34f Updated changelog and package.json for release 2019-03-26 20:56:30 -05:00
25188ddce9 To be safe, only pass the chunk of the buffer that's relevant 2019-03-26 19:16:23 -05:00
d70bc88f7c Actually fixed large message handling
We shouldn't be trying to read the message length out of the middle of a message when there are multiple segments.

Fixes https://github.com/parnic/node-screenlogic/issues/5 (for real this time, I hope...I don't have a way to test)
2019-03-26 07:10:18 -05:00
08c5457f2c Fixed handling of messages larger than 1024 bytes
Fixes https://github.com/parnic/node-screenlogic/issues/5
2019-03-25 20:02:58 -05:00
398a687cc6 Well that was obviously the wrong date 2019-02-23 10:23:05 -06:00
8727d53440 Revved for NPM 2019-02-22 18:58:10 -06:00
d3de4ec441 Updated readme
Moved changes to dedicated changelog
2019-02-22 16:06:31 -06:00
605b8e4b4e VSCode launch config 2019-02-22 16:06:20 -06:00
a09df2569b Added support for remote + passworded access
This password encoder was decompiled from the Android app then manually cleaned up and ported to Javascript. It ain't pretty, but it works.

I don't know that this is all necessarily the most correct or idiomatic way to implement these features, but I wanted to make sure the functionality got committed to help out people wanting to use this for smart home appliances (#1).
2019-02-22 16:06:11 -06:00
ba734517c9 Enable linter 2018-04-28 23:06:48 -05:00
3c6b8ebcc7 Linter fixes 2018-04-28 23:06:11 -05:00
26083f75a7 Added very basic tests 2018-04-28 22:41:38 -05:00
aac316c5ed Fixed typo 2018-04-28 22:39:39 -05:00
45 changed files with 5797 additions and 289 deletions

12
.eslintrc.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "strongloop",
"rules": {
"max-len": "off"
},
"parserOptions": {
"ecmaVersion": 6
},
"env": {
"mocha": true
}
}

69
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,69 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

30
.github/workflows/nodejs.yml vendored Normal file
View File

@ -0,0 +1,30 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm run pretest
- run: npm run test-slmessage

7
.markdownlint.json Normal file
View File

@ -0,0 +1,7 @@
{
"default": true,
"no-duplicate-header": {
"siblings_only": true
},
"line-length": false
}

20
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Example",
"program": "${workspaceFolder}/example.js",
"skipFiles": [
"<node_internals>/**"
],
"env": {
"DEBUG": "sl:*"
}
}
]
}

133
CHANGELOG.md Normal file
View File

@ -0,0 +1,133 @@
# Changelog
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.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v1.8.0 - 2022-04-17
### Added
* Added support for reading ScreenLogic time packets.
* Added support for getting system history data. This includes temperature readings over time as well as circuit on/off changes over time.
### Fixed
* Updated dependencies to safer versions of some packages.
* Fixed day-of-week conversion from Javascript Date objects to ScreenLogic times (SLTimes are 1-based starting on Sunday).
### Changed
* Alphabetized the readme.
## v1.7.0 - 2021-10-13
### Added
* Added handling/documentation for the scheduleChanged event (#44).
* Added support for getting and setting the system date/time.
### Fixed
* Documentation updates for `SLGetScheduleData`, more linkage for easy navigation, `addClient`/`removeClient` methods, clarified `coolSetPoint` for spas.
## v1.6.1 - 2020-11-23
### Added
* Every call now can optionally specify an ID that will be returned with the result, allowing tracking of simultaneous commands.
## v1.6.0 - 2020-07-14
### Added
* Fleshed out the still-undocumented `SLEquipmentConfigurationMessage` with a few more helper methods for interpreting the data inside.
* Helper method for getting a circuit from its device ID on an `SLControllerConfigMessage`.
* Support for getting the status of pumps and setting flow speeds per-pump-per-circuit.
* Constants for interpreting heat command/mode properties on various messages:
* ScreenLogic.HEAT_MODE_OFF
* ScreenLogic.HEAT_MODE_SOLAR
* ScreenLogic.HEAT_MODE_SOLARPREFERRED
* ScreenLogic.HEAT_MODE_HEATPUMP
* ScreenLogic.HEAT_MODE_DONTCHANGE
* Debug logs using the "debug" NPM package. You'll need to run an `npm install` after updating to this version.
* Ability to cancel delays in pool equipment. #20 - thanks @bshep
* Ability to register for push messages from the equipment so the connection can be kept open instead of repeatedly reconnecting and polling for changes. See the `addClient()` and `removeClient()` functions on the `UnitConnection` docs. Thanks @bshep
## v1.5.0 - 2020-06-06
### Added
* Added support for adding, deleting, listing, and updating scheduled events - thanks @bshep
* Added egg timer support - thanks @bshep
## v1.4.0 - 2020-05-25
### Added
* Support for controlling the salt cell generator's output levels.
* Helper methods for interpreting `controllerType`.
* Experimental support for an Equipment Configuration message (not documented as of yet - `SLEquipmentConfigurationMessage` / `getEquipmentConfiguration()`). This message returns arrays of various information about the equipment, but I don't know how to interpret the information in those arrays yet. Any assistance with decoding this information would be hugely helpful.
* `error` handler on all objects for reacting to unhandled node errors - thanks @schemers
### Fixed
* VSCode "Example" configuration can now be launched on non-Windows platforms.
### Changed
* Minor memory/performance optimizations.
* Running tests no longer changes any state of any pool equipment.
## v1.3.1 - 2019-12-27
### Added
* Several methods added to SLControllerConfigMessage for interpreting the equipFlags value.
### Fixed
* server.gatewayName no longer cuts off the last character of the name. #14 - thanks @mikemucc
## v1.3.0 - 2019-11-26
### Added
* Ability to set heat setpoint.
* Ability to set heat mode.
* Event for supplying incorrect parameters to `set` functions.
* Ability to send limited selection of light commands.
## v1.2.1 - 2019-03-26
### Fixed
* Messages larger than 1024 bytes are now handled properly.
## v1.2.0 - 2019-02-22
### Added
* Remote connection through Pentair servers
* Connecting to password-protected systems (this is only enforced by the ScreenLogic system on remote connections)
## v1.1.0 - 2018-04-23
### Added
* Ability to set circuit state.
### Fixed
* FindUnits.sendServerBroadcast() was failing in certain environments.
## v1.0.1 - 2018-03-31
### Added
* Direct connection support.
## v1.0.0 - 2018-03-31
* Initial release

209
PasswordEncoder.js Normal file
View File

@ -0,0 +1,209 @@
'use strict';
const BLOCK_SIZE = 16;
const MAX_KC = 8;
const MAX_ROUNDS = 14;
const sm_T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ];
const sm_T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ];
const sm_T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ];
const sm_T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ];
const sm_U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ];
const sm_U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ];
const sm_U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ];
const sm_U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ];
const sm_S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ];
const sm_rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ];
function createArray(length) {
var arr = new Array(length || 0);
var i = length;
if (arguments.length > 1) {
var args = Array.prototype.slice.call(arguments, 1);
while (i--) {
arr[length - 1 - i] = createArray.apply(this, args);
}
}
return arr;
}
exports.HLEncoder = class HLEncoder {
constructor(password) {
this.kd = createArray(MAX_ROUNDS + 1, MAX_KC);
this.ke = createArray(MAX_ROUNDS + 1, MAX_KC);
this.tk = new Array(MAX_KC);
this.bKeyInit = false;
this.iROUNDS = 0;
if (password) {
var charCodes = new Array(password.length);
for (var i = 0; i < password.length; i++) {
charCodes[i] = password.charCodeAt(i);
}
this.makeKey(charCodes);
}
}
getEncryptedPassword(cls) {
if (cls) {
var charCodes = new Array(cls.length);
for (var i = 0; i < cls.length; i++) {
charCodes[i] = cls.charCodeAt(i);
}
return this.encrypt(charCodes);
}
return null;
}
makeKey(sChallengeStr) {
this.makeKeyFromBlock(this.makeBlock(sChallengeStr, 0));
}
makeBlock(str, byFill) {
var iLen = BLOCK_SIZE;
if (str.length >= BLOCK_SIZE) {
iLen = str.length;
}
var block = new Array((Math.floor(iLen / BLOCK_SIZE) + (iLen % BLOCK_SIZE > 0 ? 1 : 0)) * BLOCK_SIZE);
for (var i = 0; i < block.length; i++) {
if (i < str.length) {
block[i] = str[i];
} else {
block[i] = byFill;
}
}
return block;
}
makeKeyFromBlock(block) {
if (!block) {
return;
}
if (block.length === 16 || block.length === 24 || block.length === 32) {
switch (block.length) {
case 16:
this.iROUNDS = 10;
break;
case 24:
this.iROUNDS = 12;
break;
default:
this.iROUNDS = MAX_ROUNDS;
break;
}
for (var round = 0; round <= this.iROUNDS; round++) {
for (var i = 0; i < 4; i++) {
this.ke[round][i] = 0;
this.kd[round][i] = 0;
}
}
var ROUND_KEY_COUNT = (this.iROUNDS + 1) * 4;
var numBlockChunks = Math.floor(block.length / 4);
for (var blockChunk = 0; blockChunk < numBlockChunks; blockChunk++) {
this.tk[blockChunk] = ((block[(blockChunk * 4) + 0] << 24) | (block[(blockChunk * 4) + 1] << 16) | (block[(blockChunk * 4) + 2] << 8) | block[(blockChunk * 4) + 3]);
}
var roundKey = 0;
for (i = 0; i < numBlockChunks && roundKey < ROUND_KEY_COUNT; i++, roundKey++) {
this.ke[Math.floor(roundKey / 4)][roundKey % 4] = this.tk[i];
this.kd[this.iROUNDS - Math.floor(roundKey / 4)][roundKey % 4] = this.tk[i];
}
for (var rconPointer = 0; roundKey < ROUND_KEY_COUNT; rconPointer++) {
var tkVal = this.tk[numBlockChunks - 1];
this.tk[0] ^= (((((sm_S[(tkVal >> 16) & 0xFF] << 24) ^ (sm_S[(tkVal >> 8) & 0xFF] << 16)) ^ (sm_S[tkVal & 0xFF] << 8)) ^ sm_S[(tkVal >> 24) & 0xFF]) ^ (sm_rcon[rconPointer] << 24));
if (numBlockChunks !== MAX_KC) {
for (var prevIdx = 0, tkIdx = 1; tkIdx < numBlockChunks; prevIdx++, tkIdx++) {
this.tk[tkIdx] ^= this.tk[prevIdx];
}
} else {
var blockChunkMidpoint = Math.floor(numBlockChunks / 2);
for (prevIdx = 0, tkIdx = 1; tkIdx < blockChunkMidpoint; prevIdx++, tkIdx++) {
this.tk[tkIdx] ^= this.tk[prevIdx];
}
tkVal = this.tk[blockChunkMidpoint - 1];
this.tk[blockChunkMidpoint] ^= (((sm_S[tkVal & 0xFF] ^ (sm_S[(tkVal >> 8) & 0xFF] << 8)) ^ (sm_S[(tkVal >> 16) & 0xFF] << 16)) ^ (sm_S[(tkVal >> 24) & 0xFF] << 24));
for (prevIdx = blockChunkMidpoint, tkIdx = blockChunkMidpoint + 1; tkIdx < numBlockChunks; prevIdx++, tkIdx++) {
this.tk[tkIdx] ^= this.tk[prevIdx];
}
}
for (tkIdx = 0; tkIdx < numBlockChunks && roundKey < ROUND_KEY_COUNT; tkIdx++, roundKey++) {
this.ke[Math.floor(roundKey / 4)][roundKey % 4] = this.tk[tkIdx];
this.kd[this.iROUNDS - Math.floor(roundKey / 4)][roundKey % 4] = this.tk[tkIdx];
}
}
for (round = 1; round < this.iROUNDS; round++) {
for (i = 0; i < 4; i++) {
var tt = this.kd[round][i];
this.kd[round][i] = ((sm_U1[(tt >> 24) & 0xFF] ^ sm_U2[(tt >> 16) & 0xFF]) ^ sm_U3[(tt >> 8) & 0xFF]) ^ sm_U4[tt & 0xFF];
}
}
this.bKeyInit = true;
}
}
encryptBlock(block) {
if (!this.bKeyInit) {
return null;
}
var a = (block[0] << 24 | block[1] << 16 | block[2] << 8 | block[3]) ^ this.ke[0][0];
var b = (block[4] << 24 | block[5] << 16 | block[6] << 8 | block[7]) ^ this.ke[0][1];
var c = (block[8] << 24 | block[9] << 16 | block[10] << 8 | block[11]) ^ this.ke[0][2];
var d = (block[12] << 24 | block[13] << 16 | block[14] << 8 | block[15]) ^ this.ke[0][3];
for (var round = 1; round < this.iROUNDS; round++) {
var a1 = sm_T1[(a >> 24 & 0xFF)] ^ sm_T2[(b >> 16 & 0xFF)] ^ sm_T3[(c >> 8 & 0xFF)] ^ sm_T4[(d & 0xFF)] ^ this.ke[round][0];
var b1 = sm_T1[(b >> 24 & 0xFF)] ^ sm_T2[(c >> 16 & 0xFF)] ^ sm_T3[(d >> 8 & 0xFF)] ^ sm_T4[(a & 0xFF)] ^ this.ke[round][1];
var c1 = sm_T1[(c >> 24 & 0xFF)] ^ sm_T2[(d >> 16 & 0xFF)] ^ sm_T3[(a >> 8 & 0xFF)] ^ sm_T4[(b & 0xFF)] ^ this.ke[round][2];
var d1 = sm_T1[(d >> 24 & 0xFF)] ^ sm_T2[(a >> 16 & 0xFF)] ^ sm_T3[(b >> 8 & 0xFF)] ^ sm_T4[(c & 0xFF)] ^ this.ke[round][3];
a = a1;
b = b1;
c = c1;
d = d1;
}
return [
(sm_S[(a >> 24 & 0xFF)] ^ this.ke[this.iROUNDS][0] >> 24), (sm_S[(b >> 16 & 0xFF)] ^ this.ke[this.iROUNDS][0] >> 16), (sm_S[(c >> 8 & 0xFF)] ^ this.ke[this.iROUNDS][0] >> 8), (sm_S[(d & 0xFF)] ^ this.ke[this.iROUNDS][0]),
(sm_S[(b >> 24 & 0xFF)] ^ this.ke[this.iROUNDS][1] >> 24), (sm_S[(c >> 16 & 0xFF)] ^ this.ke[this.iROUNDS][1] >> 16), (sm_S[(d >> 8 & 0xFF)] ^ this.ke[this.iROUNDS][1] >> 8), (sm_S[(a & 0xFF)] ^ this.ke[this.iROUNDS][1]),
(sm_S[(c >> 24 & 0xFF)] ^ this.ke[this.iROUNDS][2] >> 24), (sm_S[(d >> 16 & 0xFF)] ^ this.ke[this.iROUNDS][2] >> 16), (sm_S[(a >> 8 & 0xFF)] ^ this.ke[this.iROUNDS][2] >> 8), (sm_S[(b & 0xFF)] ^ this.ke[this.iROUNDS][2]),
(sm_S[(d >> 24 & 0xFF)] ^ this.ke[this.iROUNDS][3] >> 24), (sm_S[(a >> 16 & 0xFF)] ^ this.ke[this.iROUNDS][3] >> 16), (sm_S[(b >> 8 & 0xFF)] ^ this.ke[this.iROUNDS][3] >> 8), (sm_S[(c & 0xFF)] ^ this.ke[this.iROUNDS][3]),
];
}
encrypt(source) {
if (this.bKeyInit) {
return this.encryptData(this.makeBlock(source, 0));
}
return null;
}
encryptData(data) {
if (data == null || data.length < BLOCK_SIZE || data.length % BLOCK_SIZE !== 0) {
return null;
}
var byEncrypted = Buffer.alloc(data.length);
var iNBlocks = Math.floor(data.length / BLOCK_SIZE);
for (var iBlock = 0; iBlock < iNBlocks; iBlock++) {
var encryptedBlock = this.encryptBlock(data.slice(iBlock * BLOCK_SIZE, BLOCK_SIZE));
for (var i = 0; i < encryptedBlock.length; i++) {
byEncrypted[(iBlock * BLOCK_SIZE) + i] = encryptedBlock[i];
}
}
return byEncrypted;
}
};

732
README.md
View File

@ -1,10 +1,43 @@
# node-screenlogic
This is a Node.JS library for interfacing with Pentair ScreenLogic systems over your local network. It requires a Pentair ScreenLogic device on the same network (a network which supports UDP broadcasts).
This is a Node.JS library for interfacing with Pentair ScreenLogic systems over your local network or remotely through the Pentair dispatcher. Local connections require a Pentair ScreenLogic device on the same network (a network which supports UDP broadcasts).
Tested on node v8.11.1 with a system on firmware version 5.2 Build 736.0 Rel
Tested with a Pentair ScreenLogic system on firmware versions 5.2 Build 736.0 Rel, 5.2 Build 738.0 Rel
# Usage
* [Usage](#usage)
* [Notes](#notes)
* [Packet format](#packet-format)
* [API reference](#api-reference)
* [FindUnits](#findunits)
* [RemoteLogin](#remotelogin)
* [UnitConnection](#unitconnection)
* [All messages](#all-messages)
* [SLAddClient](#sladdclient)
* [SLAddNewScheduleEvent](#sladdnewscheduleevent)
* [SLCancelDelay](#slcanceldelay)
* [SLChemDataMessage](#slchemdatamessage)
* [SLControllerConfigMessage](#slcontrollerconfigmessage)
* [SLDeleteScheduleEventById](#sldeletescheduleeventbyid)
* [SLGetGatewayDataMessage](#slgetgatewaydatamessage)
* [SLGetHistoryData](#slgethistorydata)
* [SLGetPumpStatus](#slgetpumpstatus)
* [SLGetScheduleData](#slgetscheduledata)
* [SLGetSystemTime](#slgetsystemtime)
* [SLLightControlMessage](#sllightcontrolmessage)
* [SLPoolStatusMessage](#slpoolstatusmessage)
* [SLRemoveClient](#slremoveclient)
* [SLSaltCellConfigMessage](#slsaltcellconfigmessage)
* [SLSetCircuitRuntimeById](#slsetcircuitruntimebyid)
* [SLSetCircuitStateMessage](#slsetcircuitstatemessage)
* [SLSetHeatModeMessage](#slsetheatmodemessage)
* [SLSetHeatSetPointMessage](#slsetheatsetpointmessage)
* [SLSetPumpFlow](#slsetpumpflow)
* [SLSetSaltCellConfigMessage](#slsetsaltcellconfigmessage)
* [SLSetScheduleEventById](#slsetscheduleeventbyid)
* [SLSetSystemTime](#slsetsystemtime)
* [SLVersionMessage](#slversionmessage)
## Usage
See example.js for an example of interfacing with the library. Broadly, import the library with
@ -12,7 +45,7 @@ See example.js for an example of interfacing with the library. Broadly, import t
const ScreenLogic = require('node-screenlogic');
```
then create a new ScreenLogic unit finder with
then for local connections create a new ScreenLogic unit finder with
```javascript
new ScreenLogic.FindUnits();
@ -26,129 +59,286 @@ Hook its `serverFound` event with
and call it via `search()`. This performs a UDP broadcast on 255.255.255.255, port 1444, so ensure your network supports UDP broadcasts and the device is on the same subnet.
When a server is found, create a new UnitConnection with
Alternatively, to find a unit remotely, create a new ScreenLogic remote login with
```javascript
new ScreenLogic.RemoteLogin('Pentair: xx-xx-xx')
```
Hook its `gatewayFound` event with
```javascript
.on('gatewayFound', function(unit) { })
```
and call it via `connect()`. This opens a TCP connection to screenlogicserver.pentair.com, port 500.
When a local or remote server is found, create a new UnitConnection with
```javascript
new ScreenLogic.UnitConnection(server);
```
or
```javascript
new ScreenLogic.UnitConnection(unit.port, unit.ipAddr, '1234')
```
where `'1234'` is the remote login password.
Once you've connected with `connect()`, there are a number of methods available and corresponding events for when they've completed successfully. See [UnitConnection](#unitconnection) API reference.
All communication with a ScreenLogic unit is done via TCP, so responses will come back in the order they were requested.
# Notes
## Notes
Contributions welcome. There are lots of available messages supported by ScreenLogic that the app doesn't support yet, but can be added pretty easily as needed.
# Packet format
## Packet format
All ScreenLogic packets are sent with an 8-byte header. The first 2 bytes are a little-endian-encoded sender ID (which is normally specified when making the original request). The second 2 bytes are a little-endian-encoded message ID. The final 4 bytes are a little-endian-encoded length of the data payload on the packet. The data payload is handled per-message.
# API Reference
## API Reference
Pull requests to document undocumented properties are most welcome.
## FindUnits
### FindUnits
### constructor()
#### constructor()
Examples:
```javascript
const ScreenLogic = require('node-screenlogic');
var finder = new ScreenLogic.FindUnits();
```
### search()
#### search()
Begins a UDP broadcast search for available units.
### close()
#### close()
Closes the socket.
### Events
#### Events
* `serverFound` - Indicates that a ScreenLogic unit has been found. Event handler receives a [`UnitConnection`](#unitconnection) object.
Examples:
```javascript
finder.on('serverFound', function(server) {
var client = new ScreenLogic.UnitConnection(server);
})
```
## UnitConnection
* `error` - Indicates that an unhandled error was caught (such as the connection timing out)
### constructor(server)
### RemoteLogin
#### constructor(systemName)
Argument is the name of a system to connect to in "Pentair: xx-xx-xx" format.
Example:
```javascript
const ScreenLogic = require('./index');
var remoteLogin = new ScreenLogic.RemoteLogin('Pentair: xx-xx-xx');
```
#### connect()
Connects to the dispatcher service and searches for the unit passed to its constructor.
#### close()
Closes the connection
#### Events
* `gatewayFound` - Indicates that the search for the named unit has completed (may or may not be successful). Event handler receives a [`SLGetGatewayDataMessage`](#slgetgatewaydatamessage) argument.
* `error` - Indicates that an unhandled error was caught (such as the connection timing out)
### UnitConnection
#### constructor(server)
Argument is a server returned from [`FindUnits`](#findunits) `serverFound` event.
Examples:
```javascript
finder.on('serverFound', function(server) {
var client = new ScreenLogic.UnitConnection(server);
})
```
### constructor(port, address)
Port is an integer. Address is an IPv4 address of the server as a string.
Examples:
```javascript
var client = new ScreenLogic.UnitConnection(80, '10.0.0.85')
remoteLogin.on('gatewayFound', function(unit) {
if (unit && unit.gatewayFound) {
var client = new ScreenLogic.UnitConnection(unit.port, unit.ipAddr, '1234'));
}
});
```
### connect()
#### constructor(port, address, password)
Port is an integer. Address is an IPv4 address of the server as a string. Password is optional; should be the 4-digit password in string form, e.g. `'1234'`.
Examples:
```javascript
var client = new ScreenLogic.UnitConnection(80, '10.0.0.85', '1234')
```
#### connect()
Connects to the server passed to its constructor.
Examples:
```javascript
var client = new ScreenLogic.UnitConnection(server);
client.connect();
```
### close()
#### close()
Closes the connection.
### getVersion()
#### addClient(clientId, senderId)
Requests the system version string from the connected unit. Emits the `version` event when the response comes back.
Registers to receive updates from controller when something changes. Takes a random number `clientId` to identify the client. Emits the `poolStatus` event when something changes on the controller, and the `addClient` event when the request to add a client is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
### getPoolStatus()
#### addNewScheduleEvent(scheduleType, senderId)
Requests pool status from the connected unit. Emits the `poolStatus` event when the response comes back.
Adds a new event to the specified schedule type. See [`SLAddNewScheduleEvent`](#sladdnewscheduleevent) documentation for argument values. Emits either the `addNewScheduleEvent` or `scheduleChanged` event when response is acknowledged (listen for both). `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
### getChemicalData()
#### cancelDelay(senderId)
Requests chemical data from the connected unit (may require an IntelliChem or similar). Emits the `chemicalData` event when the response comes back.
Cancels any delays on the system. See [`SLCancelDelay`](#slcanceldelay) documentation. Emits the `cancelDelay` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
### getSaltCellConfig()
#### deleteScheduleEventById(scheduleId, senderId)
Requests salt cell status/configuration from the connected unit (requires an IntelliChlor or compatible salt cell). Emits the `saltCellConfig` event when the response comes back.
Deletes a scheduled event with specified id. See [`SLDeleteScheduleEventById`](#sldeletescheduleeventbyid) documentation for argument values. Emits the `deleteScheduleById` or `scheduleChanged` event when response is acknowledged (listen for both). `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
### getControllerConfig()
#### getChemicalData(senderId)
Requests controller configuration from the connected unit. Emits the `controllerConfig` event when the response comes back.
Requests chemical data from the connected unit (may require an IntelliChem or similar). Emits the `chemicalData` event when the response comes back. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
### setCircuitState(controllerId, circuitId, circuitState)
#### getControllerConfig(senderId)
Activates or deactivates a circuit. See [`SLSetCircuitStateMessage`](#slsetcircuitstatemessage) documentation for argument values. Emits the `circuitStateChanged` event when response is acknowledged.
Requests controller configuration from the connected unit. Emits the `controllerConfig` event when the response comes back. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### getHistoryData(fromTime, toTime, senderId)
Requests history data from the connected unit. This is information like what various temperature sensors (air, water) read over time, changes in heat setpoints, and when various circuits (pool, spa, solar, heater, and lights) were turned on and off. `fromTime` is the time (as a Javascript Date object) that you want to get events from and `toTime` is the time (as a Javascript Date object) that you want to get events until. Emits the `getHistoryDataPending` event when the request to get data is confirmed, then the `getHistoryData` event when the history data is actually ready to be handled. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### getPoolStatus(senderId)
Requests pool status from the connected unit. Emits the `poolStatus` event when the response comes back. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### getPumpStatus(pumpId, senderId)
Gets information about the specified pump. See [`SLGetPumpStatus`](#slgetpumpstatus) documentation for argument values. Emits the `getPumpStatus` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### getSaltCellConfig(senderId)
Requests salt cell status/configuration from the connected unit (requires an IntelliChlor or compatible salt cell). Emits the `saltCellConfig` event when the response comes back. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### getScheduleData(scheduleType, senderId)
Retrieves a list of schedule events of the specified type. See [`SLGetScheduleData`](#slgetscheduledata) documentation for argument values. Emits the `getScheduleData` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### getSystemTime(senderId)
Retrieves the current time the system is set to. Emits the `getSystemTime` event when response is received. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### getVersion(senderId)
Requests the system version string from the connected unit. Emits the `version` event when the response comes back. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### removeClient(clientId, senderId)
No longer receive `poolStatus` messages from controller. Emits the `removeClient` event when the request to remove a client is acknowledged. Takes a random number `clientId` that should match a previously registered client with `addClient`. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### sendLightCommand(controllerId, command, senderId)
Sends a lighting command. See [`SLLightControlMessage`](#sllightcontrolmessage) documentation for argument values. Emits the `sentLightCommand` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
Note that better/more complete handling of lighting is desired, but I have yet to find all the commands I need to implement to make that happen. This currently sends each command to all lights and there is no ability to send to an individual light. Pull requests adding more functionality here would be most welcome.
#### setCircuitRuntimebyId(circuitId, runTime, senderId)
Configures default run-time of a circuit, usually referred to as the 'egg timer'. This also applies to 'run-once' programs as this will set the length of the program. See [`SLSetCircuitRuntimeById`](#slsetcircuitruntimebyid) documentation for argument values. Emits the `setCircuitRuntimeById` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### setCircuitState(controllerId, circuitId, circuitState, senderId)
Activates or deactivates a circuit. See [`SLSetCircuitStateMessage`](#slsetcircuitstatemessage) documentation for argument values. Emits the `circuitStateChanged` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### setHeatMode(controllerId, bodyType, heatMode, senderId)
Sets the preferred heat mode. See [`SLSetHeatModeMessage`](#slsetheatmodemessage) documentation for argument values. Emits the `heatModeChanged` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### setSaltCellOutput(controllerId, poolOutput, spaOutput, senderId)
Sets the salt cell's output levels. See [`SLSetSaltCellConfigMessage`](#slsetsaltcellconfigmessage) documentation for argument values. Emits the `setSaltCellConfig` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### setScheduleEventById(scheduleId, circuitId, startTime, stopTime, dayMask, flags, heatCmd, heatSetPoint, senderId)
Configures a schedule event. See [`SLSetScheduleEventById`](#slsetscheduleeventbyid) documentation for argument values. Emits the `setScheduleEventById` or `scheduleChanged` event when response is acknowledged (listen for both). `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### setSetPoint(controllerId, bodyType, temperature, senderId)
Sets the heating setpoint for any body. See [`SLSetHeatSetPointMessage`](#slsetheatsetpointmessage) documentation for argument values. Emits the `setPointChanged` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### setPumpFlow(pumpId, circuitId, setPoint, isRPMs, senderId)
Sets flow setting for a pump/circuit combination. See [`SLSetPumpFlow`](#slsetpumpflow) documentation for argument values. Emits the `setPumpFlow` event when response is acknowledged. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
#### setSystemTime(date, adjustForDST, senderId)
Sets the current date and time of the ScreenLogic system. Emits the `setSystemTime` event when request is acknowledged. `date` must be a `Date` instance holding the date/time to set, and `adjustForDST` must be a boolean indicating whether the system should adjust for daylight saving time or not. `senderId` is an optional 16-bit integer and will be present as the `senderId` field on the returned message.
### Events
* `loggedIn` - Indicates that a connection to the server has been established and the login process completed. `get` methods can be called once this event has been emitted.
* `version` - Indicates that a response to `getVersion()` has been received. Event handler receives a [`SLVersionMessage`](#slversionmessage) object.
* `poolStatus` - Indicates that a response to `getPoolStatus()` has been received. Event handler receives a [`SLPoolStatusMessage`](#slpoolstatusmessage) object.
* `addClient` - Indicates that a response to `addClient()` has been received. Event handler receives a [`SLAddClient`](#sladdclient) object.
* `addNewScheduleEvent` - Indicates that a response to `addNewScheduleEvent()` has been received which contains the created `scheduleId` to be used later for setting up the properties. Event handler receives a [`SLAddNewScheduleEvent`](#sladdnewscheduleevent) object.
* `badParameter` - Indicates that a bad parameter has been supplied to a function. This can be triggered, for example, by sending the wrong controller ID to a `set` function.
* `cancelDelay` - Indicates that a response to `cancelDelay()` has been received. Event handler receives a [`SLCancelDelay`](#slcanceldelay) object.
* `chemicalData` - Indicates that a response to `getChemicalData()` has been received. Event handler receives a [`SLChemDataMessage`](#slchemdatamessage) object.
* `saltCellConfig` - Indicates that a response to `getSaltCellConfig()` has been received. Event handler receives a [`SLSaltCellConfigMessage`](#slsaltcellconfigmessage) object.
* `controllerConfig` - Indicates that a response to `getControllerConfig()` has been received. Event handler receives a [`SLControllerConfigMessage`](#slcontrollerconfigmessage) object.
* `circuitStateChanged` - Indicates that a response to `setCircuitState()` has been received. Event handler receives a [`SLSetCircuitStateMessage`](#slsetcircuitstatemessage) object.
* `controllerConfig` - Indicates that a response to `getControllerConfig()` has been received. Event handler receives a [`SLControllerConfigMessage`](#slcontrollerconfigmessage) object.
* `deleteScheduleById` - Indicates that a response to `deleteScheduleById()` has been received. Event handler receives a [`SLDeleteScheduleEventById`](#sldeletescheduleeventbyid) object.
* `error` - Indicates that an unhandled error was caught (such as the connection timing out)
* `getHistoryData` - Indicates that history data for the requested timeframe is ready. Event handler receives a [`SLGetHistoryData`](#slgethistorydata) object.
* `getHistoryDataPending` - Indicates that the `getHistoryData()` request has been received and is being processed.
* `getPumpStatus` - Indicates that a response to `getPumpStatus()` has been received. Event handler receives a [`SLGetPumpStatus`](#slgetpumpstatus) object.
* `getScheduleData` - Indicates that a response to `getScheduleData()` has been received. Event handler receives a [`SLGetScheduleData`](#slgetscheduledata) object.
* `getSystemTime` - Indicates that a response to `getSystemTime()` has been received. Event handler receives a [`SLGetSystemTime`](#slgetsystemtime) object.
* `heatModeChanged` - Indicates that a response to `setHeatMode()` has been received. Event handler receives a [`SLSetHeatModeMessage`](#slsetheatmodemessage) object.
* `loggedIn` - Indicates that a connection to the server has been established and the login process completed. `get` methods can be called once this event has been emitted.
* `loginFailed` - Indicates that a remote login attempt via supplying a system address and password to `UnitConnection` has failed likely due to the incorrect password being used.
* `poolStatus` - Indicates that a response to `getPoolStatus()` has been received. Event handler receives a [`SLPoolStatusMessage`](#slpoolstatusmessage) object.
* `removeClient` - Indicates that a response to `removeClient()` has been received. Event handler receives a [`SLRemoveClient`](#slremoveclient) object.
* `saltCellConfig` - Indicates that a response to `getSaltCellConfig()` has been received. Event handler receives a [`SLSaltCellConfigMessage`](#slsaltcellconfigmessage) object.
* `scheduleChanged` - Indicates that a response to adding, deleting, or setting a schedule has been received. Event handler receives nothing. This seems to be arbitrarily returned sometimes instead of a normal ack by the system.
* `sentLightCommand` - Indicates that a response to `sendLightCommand()` has been received. Event handler receives a [`SLLightControlMessage`](#sllightcontrolmessage) object.
* `setCircuitRuntimeById` - Indicates that a response to `setCircuitRuntimeById()` has been received. Event handler receives a [`SLSetCircuitRuntimeById`](#slsetcircuitruntimebyid) object.
* `setPumpFlow` - Indicates that a response to `setPumpFlow()` has been received. Event handler receives a [`SLSetPumpFlow`](#slsetpumpflow) object.
* `setPointChanged` - Indicates that a response to `setSetPoint()` has been received. Event handler receives a [`SLSetHeatSetPointMessage`](#slsetheatsetpointmessage) object.
* `setSaltCellConfig` - Indicates that a response to `setSaltCellOutput()` has been received. Event handler receives a [`SLSetSaltCellConfigMessage`](#slsetsaltcellconfigmessage) object.
* `setScheduleEventById` - Indicates that a response to `setScheduleEventById()` has been received. Event handler receives a [`SLSetScheduleEventById`](#slsetscheduleeventbyid) object.
* `setSystemTime` - Indicates that a response to `setSystemTime()` has been received. Event handler receives a [`SLSetSystemTime`](#slsetsystemtime) object if the request was valid, or `null` if the request was invalid (input parameters were not of the required types).
* `unknownCommand` - Indicates that an unknown command was issued to ScreenLogic (should not be possible to trigger when using the supplied `UnitConnection` methods).
* `version` - Indicates that a response to `getVersion()` has been received. Event handler receives a [`SLVersionMessage`](#slversionmessage) object.
### Properties
#### Properties
* `address` - string representing the IPv4 address of the found server
* `type` - integer representing the type of server found (will always be 2)
@ -157,72 +347,70 @@ Activates or deactivates a circuit. See [`SLSetCircuitStateMessage`](#slsetcircu
* `gatewaySubtype` - byte
* `gatewayName` - string representing the server's name. Will be in the format Pentair: xx-xx-xx
## SLVersionMessage
### All messages
Passed as an argument to the emitted `version` event handler.
Information about features common to all the below SL Message types.
### Properties
#### decodeTime(time)
* `version` - a string representing the system's version
Interprets a time integer recorded as minutes past midnight and returns the ScreenLogic string representation of it in 24-hour time.
## SLPoolStatusMessage
#### encodeTime(time)
Passed as an argument to the emitted `poolStatus` event handler.
Interprets the string representing 24-hour time and returns an integer of minutes past midnight.
### isDeviceReady()
#### decodeDayMask(mask)
Returns a bool indicating whether the device is in a normal operating state.
Converts a day mask from, for example, `SLGetScheduleData`'s events[idx].days property into a `DAY_VALUES` array for ease of use.
### isDeviceSync()
#### encodeDayMask(days)
Returns a bool.
Converts an array of DAY_VALUES into a mask used by, for example, `SLGetScheduleData`'s events[idx].days property.
### isDeviceServiceMode()
#### getDayValue(dayName)
Returns a bool indicating whether the device is in service mode or not.
Returns the value of a given `DAY_VALUES` day name.
### isSpaActive()
`DAY_VALUES` is defined as the following array for simplicity of checking whether a specific day is set in a mask:
Returns a bool indicating whether the spa is currently active or not.
```js
const DAY_VALUES = [
['Mon', 0x1 ],
['Tue', 0x2 ],
['Wed', 0x4 ],
['Thu', 0x8 ],
['Fri', 0x10 ],
['Sat', 0x20 ],
['Sun', 0x40 ],
];
```
### isPoolActive()
#### Properties
Returns a bool indicating whether the pool is currently active or not.
* `senderId` - an integer matching whatever was passed as the `senderId` argument when making the initial request (default 0)
* `messageId` - an integer indicating the ScreenLogic ID for this message
### Properties
### SLAddClient
* `ok` - can be interpreted with `isDevice...` methods.
* `freezeMode` - byte representing whether the device is in freeze mode or not.
* `remotes` - byte
* `poolDelay` - byte
* `spaDelay` - byte
* `cleanerDelay` - byte
* `airTemp` - integer representing the current temperature (check controller config to see if it's in celsius or fahrenheit)
* `currentTemp` - array of size 0-2 indicating current temperature of each body as an integer (pool: 0, spa: 1) (check controller config to see if it's in celsius or fahrenheit)
* `heatStatus` - array of size 0-2 indicating whether heat is active or not for each body as an integer (pool: 0, spa: 1)
* `setPoint` - array of size 0-2 holding the heating set point for each body as an integer (pool: 0, spa: 1) (check controller config to see if it's in celsius or fahrenheit)
* `coolSetPoint` - array of size 0-2 holding the cooling set point for each body as an integer (pool: 0, spa: 1) (check controller config to see if it's in celsius or fahrenheit)
* `heatMode` - array of size 0-2 indicating whether heating is enabled or not for each body as an integer (pool: 0, spa: 1)
* `circuitArray` - array holding all circuits in the system
* `id` - integer representing the circuit's ID (spa is 500, pool is 505)
* `state` - integer indicating whether the circuit is on or not (0/1)
* `colorSet` - byte
* `colorPos` - byte
* `colorStagger` - byte
* `delay` - byte
* `pH` - float indicating the current pH level (e.g.: 7.62)
* `orp` - integer indicating the current ORP value if available (e.g.: 650)
* `saturation` - float indicating the water balance/saturation level (e.g.: -0.13)
* `saltPPM` - integer indicating the salt level in parts-per-million (e.g.: 3000)
* `pHTank` - integer indicating the fill level of the pH tank (e.g.: 4)
* `orpTank` - integer indicating the fill level of the ORP tank
* `alarms` - integer indicating how many alarms are currently active
Passed as an argument to the emitted `addClient` event.
## SLChemDataMessage
### SLAddNewScheduleEvent
Passed as an argument to the emitted `addNewScheduleEvent` event. Adds a new event to the specified schedule type, either 0 for regular events or 1 for one-time events.
#### Properties
* `scheduleType` - 0 indicates regular scheduled events, 1 indicates a run-once event
### SLCancelDelay
Passed as an argument to the emitted `cancelDelay` event.
### SLChemDataMessage
Passed as an argument to the emitted `chemicalData` event handler.
### Properties
#### Properties
* `isValid` - boolean indicating whether we got a valid response back or not
* `pH` - float indicating the current pH level
@ -241,25 +429,55 @@ Passed as an argument to the emitted `chemicalData` event handler.
* `scaling` - boolean indicating whether the water balance is scaling or not
* `error` - boolean indicating whether there's currently an error in the chem system or not
## SLSaltCellConfigMessage
Passed as an argument to the emitted `saltCellConfig` event handler.
### Properties
* `installed` - boolean indicating whether a salt cell is installed or not
* `status` - integer
* `level1` - integer
* `level2` - integer
* `salt` - integer indicating salt level in parts-per-million
* `flags` - integer
* `superChlorTimer` - integer
## SLControllerConfigMessage
### SLControllerConfigMessage
Passed as an argument to the emitted `controllerConfig` event handler.
### Properties
#### hasSolar()
Returns a bool indicating whether the system has solar present. (Helper method for interpreting the value in `equipFlags`.)
#### hasSolarAsHeatpump()
Returns a bool indicating whether the system has a solar heatpump (UltraTemp, ThermalFlo) present. (Helper method for interpreting the value in `equipFlags`.)
#### hasChlorinator()
Returns a bool indicating whether the system has a chlorinator present. (Helper method for interpreting the value in `equipFlags`.)
#### hasCooling()
Returns a bool indicating whether the system has a cooler present. (Helper method for interpreting the value in `equipFlags`.)
#### hasIntellichem()
Returns a bool indicating whether the system has an IntelliChem chemical management system present. (Helper method for interpreting the value in `equipFlags`.)
#### isEasyTouch()
Returns a bool indicating whether the system is an EasyTouch system or not. (Helper method for interpreting the value in `controllerType`.)
#### isIntelliTouch()
Returns a bool indicating whether the system is an IntelliTouch system or not. (Helper method for interpreting the value in `controllerType`.)
#### isEasyTouchLite()
Returns a bool indicating whether the system is an EasyTouch Lite system or not. (Helper method for interpreting the value in `controllerType` and `hwType`.)
#### isDualBody()
Returns a bool indicating whether the system is dual-body or not. (Helper method for interpreting the value in `controllerType`.)
#### isChem2()
Returns a bool indicating whether the system is a Chem2 system or not. (Helper method for interpreting the value in `controllerType` and `hwType`.)
#### getCircuitByDeviceId(deviceId)
Returns the `bodyArray` entry for the circuit matching the given device id. This is most useful with an [`SLGetPumpStatus`](#slgetpumpstatus) message.
#### Properties
* `controllerId` - integer indicating the controller's ID
* `minSetPoint` - array (size 2) indicating the minimum setpoint available for the pool (index 0) or spa (index 1)
@ -268,7 +486,7 @@ Passed as an argument to the emitted `controllerConfig` event handler.
* `controllerType` - byte
* `hwType` - byte
* `controllerData` - byte
* `equipFlags` - integer
* `equipFlags` - integer indicating the type(s) of equipment present in the system (see helper methods above for interpreting these values)
* `genCircuitName` - string indicating the circuit name
* `bodyArray` - array (size number-of-circuits) holding circuit data
* `circuitId` - integer indicating circuit ID (e.g.: 500 is spa, 505 is pool)
@ -289,11 +507,224 @@ Passed as an argument to the emitted `controllerConfig` event handler.
* `interfaceTabFlags` - integer
* `showAlarms` - integer
## SLSetCircuitStateMessage
### SLDeleteScheduleEventById
Passed as an argument to the emitted `circuitStateChanged`. The passed version is empty, however, since the response is just an acknowledgement of receipt of the set command.
Passed as an argument to the emitted `deleteScheduleEventById` event. Deletes a scheduled event with specified id.
### Properties
#### Properties
* `scheduleId` - the scheduleId of the schedule to be deleted
### SLGetGatewayDataMessage
Passed as an argument to the emitted `gatewayFound` event. Contains information about the remote unit's status and access properties.
#### Properties
* `gatewayFound` - boolean indicating whether a unit was found
* `licenseOK` - boolean indicating if the license is valid (I've never seen this be false)
* `ipAddr` - string containing the ipv4 address to remotely connect to this unit
* `port` - number containing the port to connect to the unit
* `portOpen` - boolean indicating whether or not the port is open and able to be connected to
* `relayOn` - boolean indicating whether the relay is on (unsure what exactly this indicates; it's always been false in my tests)
### SLGetHistoryData
Passed as an argument to the emitted `getHistoryData` event. Contains information about the remote unit's temperature and circuit on/off times over time.
#### Properties
* `airTemps` - array of objects containing the air temperature over time. Each object contains a `time` key containing a Javascript Date object, and a `temp` key containing the temperature as an integer.
* `poolTemps` - array of objects containing the pool temperature over time. Each object contains a `time` key containing a Javascript Date object, and a `temp` key containing the temperature as an integer.
* `poolSetPointTemps` - array of objects containing the pool setpoint over time. Each object contains a `time` key containing a Javascript Date object, and a `temp` key containing the temperature as an integer.
* `spaTemps` - array of objects containing the spa temperature over time. Each object contains a `time` key containing a Javascript Date object, and a `temp` key containing the temperature as an integer.
* `spaSetPointTemps` - array of objects containing the spa setpoint over time. Each object contains a `time` key containing a Javascript Date object, and a `temp` key containing the temperature as an integer.
* `poolRuns` - array of objects containing the pool on/off times over time. Each object contains an `on` key containing a Javascript Date object for when the circuit turned on, and an `off` key containing a Javascript Date object for when the circuit turned off.
* `spaRuns` - array of objects containing the spa on/off times over time. Each object contains an `on` key containing a Javascript Date object for when the circuit turned on, and an `off` key containing a Javascript Date object for when the circuit turned off.
* `solarRuns` - array of objects containing the solar on/off times over time. Each object contains an `on` key containing a Javascript Date object for when the circuit turned on, and an `off` key containing a Javascript Date object for when the circuit turned off.
* `heaterRuns` - array of objects containing the heater on/off times over time. Each object contains an `on` key containing a Javascript Date object for when the circuit turned on, and an `off` key containing a Javascript Date object for when the circuit turned off.
* `lightRuns` - array of objects containing the light on/off times over time. Each object contains an `on` key containing a Javascript Date object for when the circuit turned on, and an `off` key containing a Javascript Date object for when the circuit turned off.
### SLGetPumpStatus
Passed as an argument to the emitted `getPumpStatus` event. Gets information about the specified pump.
#### Properties
* `pumpId` - id of pump to get information about, first pump is 0
#### Return Values
* `isRunning` - boolean that says if pump is running
* `pumpType` - 0 if invalid pump id or one of the IntelliFlo constants:
* ScreenLogic.PUMP_TYPE_INTELLIFLOVF
* ScreenLogic.PUMP_TYPE_INTELLIFLOVS
* ScreenLogic.PUMP_TYPE_INTELLIFLOVSF
* `pumpWatts` - current Watts usage of the pump
* `pumpRPMs` - current RPMs of the pump
* `pumpGPMs` - current GPMs of the pump
* `pumpSetting` - Array of 8 items each containing
* `circuitId` - Circuit Id (CircuitId matched data returned by [`SLControllerConfigMessage`](#slcontrollerconfigmessage)'s `getCircuitByDeviceId()`)
* `pumpSetPoint` - the set point for this pump/circuit combo (in either RPMs or GPMs depending on the value of `isRPMs`)
* `isRPMs` - boolean indicating if the set point is in RPMs (false means it's in GPMs)
* `pumpUnknown1` - unknown data; always 0
* `pumpUnknown2` - unknown data; always 255
### SLGetScheduleData
Passed as an argument to the emitted `getScheduleData` event. Retrieves a list of schedule events of the specified type, either 0 for regular events or 1 for one-time events.
#### Properties
* `eventCount` - the number of `events` returned
* `events` - array containing:
* `scheduleId` - the associated scheduleId
* `circuitId` - the circuit this schedule affects
* `startTime` - the start time of the event, specified as a string in 24-hour time, so, for example, 6:00AM would be '0600' (see [conversion functions](#decodetimetime))
* `stopTime` - the stop time of the event, specified as a string in 24-hour time, so, for example, 6:00AM would be '0600' (see [conversion functions](#decodetimetime))
* `dayMask` - 7-bit mask that determines which days the schedule is active for, MSB is always 0, valid numbers 1-127 (see [conversion functions](#decodedaymaskmask))
* `flags`
* bit 0 is the schedule type, if 0 then regular event, if 1 its a run-once
* bit 1 indicates whether heat setPoint should be changed
* `heatCmd` - integer indicating the desired heater mode. Valid values are:
* ScreenLogic.HEAT_MODE_OFF
* ScreenLogic.HEAT_MODE_SOLAR
* ScreenLogic.HEAT_MODE_SOLARPREFERRED
* ScreenLogic.HEAT_MODE_HEATPUMP
* ScreenLogic.HEAT_MODE_DONTCHANGE
* `heatSetPoint` - the temperature set point if heat is to be changed (ignored if bit 1 of flags is 0)
* `days` - which days this schedule is active for; this is just the `dayMask` property run through [`decodeDayMask()`](#decodedaymaskmask) for convenience
### SLGetSystemTime
Contains information about the system's current time and date. Passed as an argument to the emitted `getSystemTime` event.
#### Properties
* `date` - `Date` instance representing the current system datetime (preferred, the other properties are derived from this one and provided for backward compatibility purposes)
* `year` - short representing current system year
* `month` - short representing current system month (where 1 is January, 2 is February, etc.)
* `day` - short representing current system day of the month
* `dayOfWeek` - short representing current system day of the week (where 1 is Sunday and 7 is Saturday)
* `hour` - short representing current system hour (24-hour time where 0 is midnight, 13 is 1PM, etc.)
* `minute` - short representing current system minute
* `second` - short representing current system second
* `millisecond` - short representing current system millisecond
* `adjustForDST` - bool indicating whether the system should adjust for daylight saving time or not
### SLLightControlMessage
Passed as an argument to the emitted `sentLightCommand` event.
#### Properties
* `controllerId` - integer indicating the ID of the controller to send this command to.
* Note that while `SLControllerConfigMessage` includes a controllerId, this ID, in my experience, should always be 0.
* `command` - integer indicating which command to send to the lights. Valid values are:
* ScreenLogic.LIGHT_CMD_LIGHTS_OFF
* ScreenLogic.LIGHT_CMD_LIGHTS_ON
* ScreenLogic.LIGHT_CMD_COLOR_SET
* ScreenLogic.LIGHT_CMD_COLOR_SYNC
* ScreenLogic.LIGHT_CMD_COLOR_SWIM
* ScreenLogic.LIGHT_CMD_COLOR_MODE_PARTY
* ScreenLogic.LIGHT_CMD_COLOR_MODE_ROMANCE
* ScreenLogic.LIGHT_CMD_COLOR_MODE_CARIBBEAN
* ScreenLogic.LIGHT_CMD_COLOR_MODE_AMERICAN
* ScreenLogic.LIGHT_CMD_COLOR_MODE_SUNSET
* ScreenLogic.LIGHT_CMD_COLOR_MODE_ROYAL
* ScreenLogic.LIGHT_CMD_COLOR_SET_SAVE
* ScreenLogic.LIGHT_CMD_COLOR_SET_RECALL
* ScreenLogic.LIGHT_CMD_COLOR_BLUE
* ScreenLogic.LIGHT_CMD_COLOR_GREEN
* ScreenLogic.LIGHT_CMD_COLOR_RED
* ScreenLogic.LIGHT_CMD_COLOR_WHITE
* ScreenLogic.LIGHT_CMD_COLOR_PURPLE
### SLPoolStatusMessage
Passed as an argument to the emitted `poolStatus` event handler.
#### isDeviceReady()
Returns a bool indicating whether the device is in a normal operating state.
#### isDeviceSync()
Returns a bool.
#### isDeviceServiceMode()
Returns a bool indicating whether the device is in service mode or not.
#### isSpaActive()
Returns a bool indicating whether the spa is currently active or not.
#### isPoolActive()
Returns a bool indicating whether the pool is currently active or not.
#### Properties
* `ok` - can be interpreted with `isDevice...` methods.
* `freezeMode` - byte representing whether the device is in freeze mode or not.
* `remotes` - byte
* `poolDelay` - byte
* `spaDelay` - byte
* `cleanerDelay` - byte
* `airTemp` - integer representing the current temperature (check controller config to see if it's in celsius or fahrenheit)
* `currentTemp` - array of size 0-2 indicating current temperature of each body as an integer (pool: 0, spa: 1) (check controller config to see if it's in celsius or fahrenheit)
* `heatStatus` - array of size 0-2 indicating whether heat is active or not for each body as an integer (pool: 0, spa: 1)
* `setPoint` - array of size 0-2 holding the heating set point for each body as an integer (pool: 0, spa: 1) (check controller config to see if it's in celsius or fahrenheit)
* `coolSetPoint` - array of size 0-2 holding the cooling set point for each body as an integer (pool: 0, spa: 1; the spa seems to always track air temperature for this, however) (check controller config to see if it's in celsius or fahrenheit)
* `heatMode` - array of size 0-2 indicating whether heating is enabled or not for each body as an integer (pool: 0, spa: 1)
* `circuitArray` - array holding all circuits in the system
* `id` - integer representing the circuit's ID (spa is 500, pool is 505)
* `state` - integer indicating whether the circuit is on or not (0/1)
* `colorSet` - byte
* `colorPos` - byte
* `colorStagger` - byte
* `delay` - byte
* `pH` - float indicating the current pH level (e.g.: 7.62)
* `orp` - integer indicating the current ORP value if available (e.g.: 650)
* `saturation` - float indicating the water balance/saturation level (e.g.: -0.13)
* `saltPPM` - integer indicating the salt level in parts-per-million (e.g.: 3000)
* `pHTank` - integer indicating the fill level of the pH tank (e.g.: 4)
* `orpTank` - integer indicating the fill level of the ORP tank
* `alarms` - integer indicating how many alarms are currently active
### SLRemoveClient
Passed as an argument to the emitted `removeClient` event.
### SLSaltCellConfigMessage
Passed as an argument to the emitted `saltCellConfig` event handler.
#### Properties
* `installed` - boolean indicating whether a salt cell is installed or not
* `status` - integer bitmask
* `level1` - integer indicating the output level of the salt cell for the pool. I believe this operates on a 0-100 scale
* `level2` - integer indicating the output level of the salt cell for the spa. I believe this operates on a 0-100 scale
* `salt` - integer indicating salt level in parts-per-million
* `flags` - integer bitmask
* `superChlorTimer` - integer
### SLSetCircuitRuntimeById
Passed as an argument to the emitted `setCircuitRuntimebyId` event. Configures default run-time of a circuit, usually referred to as the 'egg timer'. This also applies to 'run-once' programs as this will set the length of the program.
#### Properties
* `circuitId` - id of the circuit to which this event applies to
* `runTime` - integer specifying the run time in minutes
### SLSetCircuitStateMessage
Passed as an argument to the emitted `circuitStateChanged` event.
#### Properties
* `controllerId` - integer indicating the ID of the controller to send this command to.
* Note that while `SLControllerConfigMessage` includes a controllerId, this ID, in my experience, should always be 0.
@ -301,11 +732,88 @@ Passed as an argument to the emitted `circuitStateChanged`. The passed version i
* This ID can be retrieved from `SLControllerConfigMessage`'s `bodyArray` property.
* `circuitState` - integer indicating whether to switch the circuit on (`1`) or off (`0`).
# Changelog
### SLSetHeatModeMessage
## v1.0.1
* Added direct connection support.
Passed as an argument to the emitted `heatModeChanged` event.
## v1.1.0
* Added ability to set circuit state.
* Fixed FindUnits.sendServerBroadcast() failing in certain environments.
#### Properties
* `controllerId` - integer indicating the ID of the controller to send this command to.
* Note that while `SLControllerConfigMessage` includes a controllerId, this ID, in my experience, should always be 0.
* `bodyType` - integer indicating the type of body to set the setpoint of. The pool is body `0` and the spa is body `1`.
* `heatMode` - integer indicating the desired heater mode. Valid values are:
* ScreenLogic.HEAT_MODE_OFF
* ScreenLogic.HEAT_MODE_SOLAR
* ScreenLogic.HEAT_MODE_SOLARPREFERRED
* ScreenLogic.HEAT_MODE_HEATPUMP
* ScreenLogic.HEAT_MODE_DONTCHANGE
### SLSetHeatSetPointMessage
Passed as an argument to the emitted `setPointChanged` event.
#### Properties
* `controllerId` - integer indicating the ID of the controller to send this command to.
* Note that while `SLControllerConfigMessage` includes a controllerId, this ID, in my experience, should always be 0.
* `bodyType` - integer indicating the type of body to set the setpoint of. The pool is body `0` and the spa is body `1`.
* `temperature` - integer indicating the desired setpoint. This is presumably in whatever units your system is set to (celsius or fahrenheit).
### SLSetPumpFlow
Passed as an argument to the emitted `setPumpFlow` event. Sets flow setting for a pump/circuit combination.
#### Properties
* `pumpId` - id of pump to get information about, first pump is 0
* `circuitId` - index of circuit for which to change the set point (index is relative to data returned by [`SLGetPumpStatus`](#slgetpumpstatus))
* `setPoint` - the value for which to set the pump/circuit combo
* `isRPMs` - boolean, `true` for RPMs, `false` for GPMs
### SLSetSaltCellConfigMessage
Passed as an argument to the emitted `setSaltCellConfig` event.
#### Properties
* `controllerId` - integer indicating the ID of the controller to send this command to.
* Note that while `SLControllerConfigMessage` includes a controllerId, this ID, in my experience, should always be 0.
* `poolOutput` - integer indicating the output level of the salt cell for the pool. I believe this operates on a 0-100 scale.
* `spaOutput` - integer indicating the output level of the salt cell for the spa. I believe this operates on a 0-100 scale.
### SLSetScheduleEventById
Passed as an argument to the emitted `setScheduleEventById` event. Configures an event with properties as described below.
#### Properties
* `scheduleId` - id of a schedule previously created, see [`SLAddNewScheduleEvent`](#sladdnewscheduleevent)
* `circuitId` - id of the circuit to which this event applies
* `startTime` - the start time of the event, specified as minutes since midnight (see [conversion functions](#encodetimetime))
* example: 6:00am would be 360
* example: 6:15am would be 375
* `stopTime` - the stop time of the event, specified as minutes since midnight (see [conversion functions](#encodetimetime))
* `dayMask`
* 7-bit mask that determines which days the schedule is active for, MSB is always 0, valid numbers 1-127
* `flags`
* bit 0 is the schedule type, if 0 then regular event, if 1 its a run-once
* bit 1 indicates whether heat setPoint should be changed
* `heatCmd` - integer indicating the desired heater mode. Valid values are:
* ScreenLogic.HEAT_MODE_OFF
* ScreenLogic.HEAT_MODE_SOLAR
* ScreenLogic.HEAT_MODE_SOLARPREFERRED
* ScreenLogic.HEAT_MODE_HEATPUMP
* ScreenLogic.HEAT_MODE_DONTCHANGE
* `heatSetPoint` - the temperature set point if heat is to be changed (ignored if bit 1 of flags is 0)
### SLSetSystemTime
Passed as an argument to the emitted `setSystemTime` event.
### SLVersionMessage
Passed as an argument to the emitted `version` event handler.
#### Properties
* `version` - a string representing the system's version

View File

@ -1,41 +1,66 @@
'use strict';
const ScreenLogic = require('./index');
var finder = new ScreenLogic.FindUnits();
finder.on('serverFound', function(server) {
finder.close();
connect(new ScreenLogic.UnitConnection(server));
// use this to find and connect to units local to the network this is running on
// var finder = new ScreenLogic.FindUnits();
// finder.on('serverFound', function(server) {
// finder.close();
// connect(new ScreenLogic.UnitConnection(server));
// });
//
// finder.search();
// use this if you want to use a direct connection to a known unit
// connect(new ScreenLogic.UnitConnection(80, '10.0.0.85'));
// use this to remote connect to a system by name (going through the Pentair servers)
const systemName = 'Pentair: xx-xx-xx';
const password = '1234';
var remote = new ScreenLogic.RemoteLogin(systemName);
remote.on('gatewayFound', function(unit) {
remote.close();
if (unit && unit.gatewayFound) {
console.log('unit ' + remote.systemName + ' found at ' + unit.ipAddr + ':' + unit.port);
connect(new ScreenLogic.UnitConnection(unit.port, unit.ipAddr, password));
} else {
console.log('no unit found by that name');
}
});
finder.search();
// use this instead of the above `finder` logic if you want to use a direct connection
//connect(new ScreenLogic.UnitConnection(80, '10.0.0.85'));
remote.connect();
// generic connection method used by all above examples
function connect(client) {
client.on('loggedIn', function() {
this.getVersion();
}).on('version', function(version) {
this.getPoolStatus();
console.log(" version=" + version.version);
console.log(' version=' + version.version);
}).on('poolStatus', function(status) {
this.getChemicalData();
console.log(" pool ok=" + status.ok);
console.log(" air temp=" + status.airTemp);
console.log(" salt ppm=" + status.saltPPM);
console.log(" pH=" + status.pH);
console.log(" saturation=" + status.saturation);
console.log(" spa active=" + status.isSpaActive());
console.log(" pool active=" + status.isPoolActive());
console.log(' pool ok=' + status.ok);
console.log(' pool temp=' + status.currentTemp[0]);
console.log(' air temp=' + status.airTemp);
console.log(' salt ppm=' + status.saltPPM);
console.log(' pH=' + status.pH);
console.log(' saturation=' + status.saturation);
console.log(' spa active=' + status.isSpaActive());
console.log(' pool active=' + status.isPoolActive());
}).on('chemicalData', function(chemData) {
this.getSaltCellConfig();
console.log(" calcium=" + chemData.calcium);
console.log(" cyanuric acid=" + chemData.cyanuricAcid);
console.log(" alkalinity=" + chemData.alkalinity);
console.log(' calcium=' + chemData.calcium);
console.log(' cyanuric acid=' + chemData.cyanuricAcid);
console.log(' alkalinity=' + chemData.alkalinity);
}).on('saltCellConfig', function(saltCellConfig) {
this.getControllerConfig();
console.log(" salt cell installed=" + saltCellConfig.installed);
console.log(' salt cell installed=' + saltCellConfig.installed);
}).on('controllerConfig', function(config) {
console.log(" controller is in celsius=" + config.degC);
console.log(' controller is in celsius=' + config.degC);
client.close();
}).on('loginFailed', function() {
console.log(' unable to login (wrong password?)');
client.close();
});

49
heatmodetest.js Executable file
View File

@ -0,0 +1,49 @@
'use strict';
const ScreenLogic = require('./index');
// use this to find and connect to units local to the network this is running on
var finder = new ScreenLogic.FindUnits();
finder.on('serverFound', function(server) {
finder.close();
connect(new ScreenLogic.UnitConnection(server));
});
finder.search();
// use this if you want to use a direct connection to a known unit
// connect(new ScreenLogic.UnitConnection(80, '10.0.0.85'));
// use this to remote connect to a system by name (going through the Pentair servers)
// const systemName = 'Pentair: xx-xx-xx';
// const password = '1234';
// var remote = new ScreenLogic.RemoteLogin(systemName);
// remote.on('gatewayFound', function(unit) {
// remote.close();
// if (unit && unit.gatewayFound) {
// console.log('unit ' + remote.systemName + ' found at ' + unit.ipAddr + ':' + unit.port);
// connect(new ScreenLogic.UnitConnection(unit.port, unit.ipAddr, password));
// } else {
// console.log('no unit found by that name');
// }
// });
// remote.connect();
// generic connection method used by all above examples
function connect(client) {
client.on('loggedIn', function() {
this.getVersion();
}).on('version', function(version) {
this.setHeatMode(0, 0, 3);
console.log(' version=' + version.version);
}).on('heatModeChanged', function() {
client.close();
}).on('loginFailed', function() {
console.log(' unable to login (wrong password?)');
client.close();
});
client.connect();
}

409
index.js
View File

@ -1,17 +1,26 @@
'use strict';
var dgram = require('dgram');
var net = require('net');
const EventEmitter = require('events');
const messages = require('./messages');
const Encoder = require('./PasswordEncoder').HLEncoder;
var debugFind = require('debug')('sl:find');
var debugRemote = require('debug')('sl:remote');
var debugUnit = require('debug')('sl:unit');
class FindUnits extends EventEmitter {
constructor() {
super();
this.finder = dgram.createSocket('udp4');
var _this = this;
this.finder.on('message', function (message, remote) {
this.finder.on('message', function(message, remote) {
_this.foundServer(message, remote);
}).on('close', function() {
//console.log('finder closed');
debugFind('closed');
}).on('error', function(e) {
debugFind('error: %O', e);
_this.emit('error', e);
});
}
@ -25,7 +34,7 @@ class FindUnits extends EventEmitter {
}
foundServer(message, remote) {
//console.log('Found something');
debugFind('found something');
if (message.length >= 40) {
var server = {
address: remote.address,
@ -33,21 +42,23 @@ class FindUnits extends EventEmitter {
port: message.readInt16LE(8),
gatewayType: message.readUInt8(10),
gatewaySubtype: message.readUInt8(11),
gatewayName: message.toString('utf8', 12, 28)
gatewayName: message.toString('utf8', 12, 29),
};
//console.log(' type: ' + server.type + ', host: ' + server.address + ':' + server.port + ', identified as ' + server.gatewayName);
debugFind(' type: ' + server.type + ', host: ' + server.address + ':' + server.port + ', identified as ' + server.gatewayName);
if (server.type === 2) {
this.emit('serverFound', server);
}
} else {
debugFind(' unexpected message');
}
}
sendServerBroadcast() {
var message = Buffer.alloc(8);
message[0] = 1;
this.finder.send(message, 0, message.length, 1444, "255.255.255.255");
//console.log("Looking for ScreenLogic hosts...");
this.finder.send(message, 0, message.length, 1444, '255.255.255.255');
debugFind('Looking for ScreenLogic hosts...');
}
close() {
@ -55,8 +66,62 @@ class FindUnits extends EventEmitter {
}
}
class RemoteLogin extends EventEmitter {
constructor(systemName) {
super();
this.systemName = systemName;
this.client = new net.Socket();
var _this = this;
this.client.on('data', function(msg) {
_this.onClientMessage(msg);
}).on('close', function(had_error) {
debugRemote('remote login server connection closed');
}).on('error', function(e) {
debugRemote('error: %o', e);
_this.emit('error', e);
});
}
connect() {
debugRemote('connecting to dispatcher...');
var _this = this;
this.client.connect(500, 'screenlogicserver.pentair.com', function() {
_this.onConnected();
});
}
onConnected() {
debugRemote('connected to dispatcher');
this.client.write(new messages.SLGetGatewayDataMessage(this.systemName).toBuffer());
}
onClientMessage(msg) {
debugRemote('received message of length ' + msg.length);
if (msg.length < 4) {
return;
}
var msgType = msg.readInt16LE(2);
switch (msgType) {
case messages.SLGetGatewayDataMessage.getResponseId():
debugRemote(" it's a gateway response");
this.emit('gatewayFound', new messages.SLGetGatewayDataMessage(msg));
break;
default:
debugRemote(" it's unknown. type: " + msgType);
break;
}
}
close() {
this.client.end();
}
}
class UnitConnection extends EventEmitter {
constructor(server, address) {
constructor(server, address, password) {
super();
if (typeof server === 'object') {
this.serverPort = server.port;
@ -66,12 +131,35 @@ class UnitConnection extends EventEmitter {
this.serverAddress = address;
}
this.password = password;
this.client = new net.Socket();
this.client.setKeepAlive(true, 10 * 1000);
var _this = this;
var buffer = Buffer.alloc(1024);
var bufferIdx = 0;
var expectedMsgLen = 0;
this.client.on('data', function(msg) {
_this.onClientMessage(msg);
if (buffer.length < msg.length + bufferIdx) {
buffer = Buffer.alloc(msg.length + buffer.length, buffer);
}
if (bufferIdx === 0) {
expectedMsgLen = msg.readInt32LE(4) + 8;
}
msg.copy(buffer, bufferIdx);
bufferIdx = bufferIdx + msg.length;
if (bufferIdx === expectedMsgLen) {
_this.onClientMessage(buffer.slice(0, expectedMsgLen));
bufferIdx = 0;
}
}).on('close', function(had_error) {
//console.log('unit connection closed');
debugUnit('closed');
}).on('error', function(e) {
debugUnit('error: %o', e);
_this.emit('error', e);
});
}
@ -80,7 +168,7 @@ class UnitConnection extends EventEmitter {
}
connect() {
//console.log("connecting...");
debugUnit('connecting...');
var _this = this;
this.client.connect(this.serverPort, this.serverAddress, function() {
_this.onConnected();
@ -88,51 +176,156 @@ class UnitConnection extends EventEmitter {
}
onConnected() {
//console.log('connected');
debugUnit('connected');
//console.log('sending init message...');
debugUnit('sending init message...');
this.client.write('CONNECTSERVERHOST\r\n\r\n');
//console.log('sending challenge message...');
debugUnit('sending challenge message...');
this.client.write(new messages.SLChallengeMessage().toBuffer());
}
login() {
//console.log('sending login message...');
this.client.write(new messages.SLLoginMessage().toBuffer());
debugUnit('sending login message...');
var password = new Encoder(this.password).getEncryptedPassword(this.challengeString);
this.client.write(new messages.SLLoginMessage(password).toBuffer());
}
getPoolStatus() {
//console.log('sending pool status query...');
this.client.write(new messages.SLPoolStatusMessage().toBuffer());
getPoolStatus(senderId) {
debugUnit('[%d] sending pool status query...', senderId || 0);
this.client.write(new messages.SLPoolStatusMessage(null, senderId).toBuffer());
}
getControllerConfig() {
//console.log('sending controller config query...');
this.client.write(new messages.SLControllerConfigMessage().toBuffer());
getControllerConfig(senderId) {
debugUnit('[%d] sending controller config query...', senderId || 0);
this.client.write(new messages.SLControllerConfigMessage(null, senderId).toBuffer());
}
getChemicalData() {
//console.log('sending chemical data query...');
this.client.write(new messages.SLChemDataMessage().toBuffer());
getChemicalData(senderId) {
debugUnit('[%d] sending chemical data query...', senderId || 0);
this.client.write(new messages.SLChemDataMessage(null, senderId).toBuffer());
}
getSaltCellConfig() {
//console.log('sending salt cell config query...');
this.client.write(new messages.SLSaltCellConfigMessage().toBuffer());
getSaltCellConfig(senderId) {
debugUnit('[%d] sending salt cell config query...', senderId || 0);
this.client.write(new messages.SLSaltCellConfigMessage(null, senderId).toBuffer());
}
getVersion() {
//console.log('sending version query...');
this.client.write(new messages.SLVersionMessage().toBuffer());
getVersion(senderId) {
debugUnit('[%d] sending version query...', senderId || 0);
this.client.write(new messages.SLVersionMessage(null, senderId).toBuffer());
}
setCircuitState(controllerId, circuitId, circuitState) {
this.client.write(new messages.SLSetCircuitStateMessage(controllerId, circuitId, circuitState).toBuffer());
getEquipmentConfiguration(senderId) {
debugUnit('[%d] sending equipment configuration query...', senderId || 0);
this.client.write(new messages.SLEquipmentConfigurationMessage(null, senderId).toBuffer());
}
setCircuitState(controllerId, circuitId, circuitState, senderId) {
debugUnit('[%d] sending set circuit state command: controllerId: %d, circuitId: %d, circuitState: %d...', senderId || 0, controllerId, circuitId, circuitState);
this.client.write(new messages.SLSetCircuitStateMessage(controllerId, circuitId, circuitState, senderId).toBuffer());
}
setSetPoint(controllerId, bodyType, temperature, senderId) {
debugUnit('[%d] sending set setpoint command: controllerId: %d, bodyType: %d, temperature: %d...', senderId || 0, controllerId, bodyType, temperature);
this.client.write(new messages.SLSetHeatSetPointMessage(controllerId, bodyType, temperature, senderId).toBuffer());
}
setHeatMode(controllerId, bodyType, heatMode, senderId) {
debugUnit('[%d] sending set heatmode command: controllerId: %d, bodyType: %d, heatMode: %d...', senderId || 0, controllerId, bodyType, heatMode);
this.client.write(new messages.SLSetHeatModeMessage(controllerId, bodyType, heatMode, senderId).toBuffer());
}
sendLightCommand(controllerId, command, senderId) {
debugUnit('[%d] sending light command: controllerId: %d, command: %d...', senderId || 0, controllerId, command);
this.client.write(new messages.SLLightControlMessage(controllerId, command, senderId).toBuffer());
}
setSaltCellOutput(controllerId, poolOutput, spaOutput, senderId) {
debugUnit('[%d] sending set saltcell output command: controllerId: %d, poolOutput: %d, spaOutput: %d...', senderId || 0, controllerId, poolOutput, spaOutput);
this.client.write(new messages.SLSetSaltCellConfigMessage(controllerId, poolOutput, spaOutput, senderId).toBuffer());
}
getScheduleData(scheduleType, senderId) {
debugUnit('[%d] sending set schedule data query for scheduleType: %d...', senderId || 0, scheduleType);
this.client.write(new messages.SLGetScheduleData(null, scheduleType, senderId).toBuffer());
}
addNewScheduleEvent(scheduleType, senderId) {
debugUnit('[%d] sending add new schedule event command for scheduleType: %d...', senderId || 0, scheduleType);
this.client.write(new messages.SLAddNewScheduleEvent(null, scheduleType, senderId).toBuffer());
}
deleteScheduleEventById(scheduleId, senderId) {
debugUnit('[%d] sending delete schedule event command for scheduleId: %d...', senderId || 0, scheduleId);
this.client.write(new messages.SLDeleteScheduleEventById(scheduleId, senderId).toBuffer());
}
// todo: should this just accept a SLSetScheduleEventById message instead of all these args?
setScheduleEventById(scheduleId, circuitId, startTime, stopTime, dayMask, flags, heatCmd, heatSetPoint, senderId) {
debugUnit('[%d] sending set schedule event command for scheduleId: %d, circuitId: %d, startTime: %d, stopTime: %d, dayMask: %d, flags: %d, heatCmd: %d, heatSetPoint: %d...', senderId || 0, scheduleId, circuitId, startTime, stopTime, dayMask, flags, heatCmd, heatSetPoint);
this.client.write(new messages.SLSetScheduleEventById(null, scheduleId, circuitId, startTime, stopTime, dayMask, flags, heatCmd, heatSetPoint, senderId).toBuffer());
}
setCircuitRuntimebyId(circuitId, runTime, senderId) {
debugUnit('[%d] sending set circuit runtime command for circuitId: %d, runTime: %d...', senderId || 0, circuitId, runTime);
this.client.write(new messages.SLSetCircuitRuntimeById(circuitId, runTime, senderId).toBuffer());
}
getPumpStatus(pumpId, senderId) {
debugUnit('[%d] sending get pump status command for pumpId: %d...', senderId || 0, pumpId);
this.client.write(new messages.SLGetPumpStatus(null, pumpId, senderId).toBuffer());
}
setPumpFlow(pumpId, circuitId, setPoint, isRPMs, senderId) {
debugUnit('[%d] sending set pump flow command for pumpId: %d, circuitId: %d, setPoint: %d, isRPMs: %d...', senderId || 0, pumpId, circuitId, setPoint, isRPMs);
this.client.write(new messages.SLSetPumpFlow(pumpId, circuitId, setPoint, isRPMs, senderId).toBuffer());
}
cancelDelay(senderId) {
debugUnit('[%d] sending cancel delay command...', senderId || 0);
this.client.write(new messages.SLCancelDelay(senderId).toBuffer());
}
addClient(clientId, senderId) {
debugUnit('[%d] sending add client command, clientId %d...', senderId || 0, clientId);
this.client.write(new messages.SLAddClient(clientId, senderId).toBuffer());
}
removeClient(clientId, senderId) {
debugUnit('[%d] sending remove client command, clientId %d...', senderId || 0, clientId);
this.client.write(new messages.SLRemoveClient(clientId, senderId).toBuffer());
}
getSystemTime(senderId) {
debugUnit('[%d] sending get system time query...', senderId || 0);
this.client.write(new messages.SLGetSystemTime(null, senderId).toBuffer());
}
setSystemTime(date, shouldAdjustForDST, senderId) {
if (!(date instanceof Date)) {
debugUnit('setSystemTime() must receive valid Date object for the date argument');
this.emit('setSystemTime', null);
return;
}
if (typeof shouldAdjustForDST !== 'boolean') {
debugUnit('setSystemTime() must receive a boolean for the shouldAdjustForDST argument');
this.emit('setSystemTime', null);
return;
}
debugUnit('[%d] sending set system time command...', senderId || 0);
this.client.write(new messages.SLSetSystemTime(null, date, shouldAdjustForDST, senderId).toBuffer());
}
getHistoryData(fromTime, toTime, senderId) {
debugUnit('[%d] requesting history data from `%s` to `%s`', senderId || 0, fromTime || new Date(), toTime || new Date());
this.client.write(new messages.SLGetHistoryData(null, fromTime, toTime, senderId).toBuffer());
}
onClientMessage(msg) {
//console.log('received message of length ' + msg.length);
debugUnit('received message of length %d', msg.length);
if (msg.length < 4) {
return;
}
@ -140,39 +333,136 @@ class UnitConnection extends EventEmitter {
var msgType = msg.readInt16LE(2);
switch (msgType) {
case messages.SLChallengeMessage.getResponseId():
//console.log(" it's a challenge response");
debugUnit(" it's a challenge response");
this.challengeString = new messages.SLChallengeMessage(msg).challengeString;
this.login();
break;
case messages.SLLoginMessage.getResponseId():
//console.log(" it's a login response");
debugUnit(" it's a login response");
this.emit('loggedIn');
break;
case messages.SLPoolStatusMessage.getResponseId():
//console.log(" it's pool status");
debugUnit(" it's pool status");
this.emit('poolStatus', new messages.SLPoolStatusMessage(msg));
break;
case messages.SLControllerConfigMessage.getResponseId():
//console.log(" it's controller configuration");
debugUnit(" it's controller configuration");
this.emit('controllerConfig', new messages.SLControllerConfigMessage(msg));
break;
case messages.SLChemDataMessage.getResponseId():
//console.log(" it's chem data");
debugUnit(" it's chem data");
this.emit('chemicalData', new messages.SLChemDataMessage(msg));
break;
case messages.SLSaltCellConfigMessage.getResponseId():
//console.log(" it's salt cell config");
debugUnit(" it's salt cell config");
this.emit('saltCellConfig', new messages.SLSaltCellConfigMessage(msg));
break;
case messages.SLVersionMessage.getResponseId():
//console.log(" it's version");
debugUnit(" it's version");
this.emit('version', new messages.SLVersionMessage(msg));
break;
case messages.SLSetCircuitStateMessage.getResponseId():
//console.log(" it's circuit toggle ack");
this.emit('circuitStateChanged', new messages.SLSetCircuitStateMessage());
debugUnit(" it's circuit toggle ack");
this.emit('circuitStateChanged', new messages.SLSetCircuitStateMessage(msg));
break;
case messages.SLSetHeatSetPointMessage.getResponseId():
debugUnit(" it's a setpoint ack");
this.emit('setPointChanged', new messages.SLSetHeatSetPointMessage(msg));
break;
case messages.SLSetHeatModeMessage.getResponseId():
debugUnit(" it's a heater mode ack");
this.emit('heatModeChanged', new messages.SLSetHeatModeMessage(msg));
break;
case messages.SLLightControlMessage.getResponseId():
debugUnit(" it's a light control ack");
this.emit('sentLightCommand', new messages.SLLightControlMessage(msg));
break;
case messages.SLSetSaltCellConfigMessage.getResponseId():
debugUnit(" it's a set salt cell config ack");
this.emit('setSaltCellConfig', new messages.SLSetSaltCellConfigMessage(msg));
break;
case messages.SLEquipmentConfigurationMessage.getResponseId():
debugUnit(" it's equipment configuration");
this.emit('equipmentConfiguration', new messages.SLEquipmentConfigurationMessage(msg));
break;
case messages.SLGetScheduleData.getResponseId():
debugUnit(" it's schedule data");
this.emit('getScheduleData', new messages.SLGetScheduleData(msg));
break;
case messages.SLAddNewScheduleEvent.getResponseId():
debugUnit(" it's a new schedule event ack");
this.emit('addNewScheduleEvent', new messages.SLAddNewScheduleEvent(msg));
break;
case messages.SLDeleteScheduleEventById.getResponseId():
debugUnit(" it's a delete schedule event ack");
this.emit('deleteScheduleEventById', new messages.SLDeleteScheduleEventById(msg));
break;
case messages.SLSetScheduleEventById.getResponseId():
debugUnit(" it's a set schedule event ack");
this.emit('setScheduleEventById', new messages.SLSetScheduleEventById(msg));
break;
case messages.SLSetCircuitRuntimeById.getResponseId():
debugUnit(" it's a set circuit runtime ack");
this.emit('setCircuitRuntimebyId', new messages.SLSetCircuitRuntimeById(msg));
break;
case messages.SLGetPumpStatus.getResponseId():
debugUnit(" it's pump status");
this.emit('getPumpStatus', new messages.SLGetPumpStatus(msg));
break;
case messages.SLSetPumpFlow.getResponseId():
debugUnit(" it's a set pump flow ack");
this.emit('setPumpFlow', new messages.SLSetPumpFlow(msg));
break;
case messages.SLCancelDelay.getResponseId():
debugUnit(" it's a cancel delay ack");
this.emit('cancelDelay', new messages.SLCancelDelay(msg));
break;
case messages.SLAddClient.getResponseId():
debugUnit(" it's an add client ack");
this.emit('addClient', new messages.SLAddClient(msg));
break;
case messages.SLRemoveClient.getResponseId():
debugUnit(" it's a remove client ack");
this.emit('removeClient', new messages.SLRemoveClient(msg));
break;
case messages.SLPoolStatusMessage.getAsyncResponseId():
debugUnit(" it's async pool status");
this.emit('poolStatus', new messages.SLPoolStatusMessage(msg));
break;
case messages.SLGetSystemTime.getResponseId():
debugUnit(" it's system time");
this.emit('getSystemTime', new messages.SLGetSystemTime(msg));
break;
case messages.SLSetSystemTime.getResponseId():
debugUnit(" it's a set system time ack");
this.emit('setSystemTime', new messages.SLSetSystemTime(msg));
break;
case messages.SLGetHistoryData.getResponseId():
debugUnit(" it's a history data query ack");
this.emit('getHistoryDataPending');
break;
case messages.SLGetHistoryData.getPayloadId():
debugUnit(" it's a history data payload");
this.emit('getHistoryData', new messages.SLGetHistoryData(msg));
break;
case 12501:
debugUnit(" it's a schedule changed notification");
this.emit('scheduleChanged');
break;
case 13:
debugUnit(" it's a login failure.");
this.emit('loginFailed');
break;
case 30:
debugUnit(" it's an unknown command.");
this.emit('unknownCommand');
break;
case 31:
debugUnit(" it's a parameter failure.");
this.emit('badParameter');
break;
default:
//console.log(" it's unknown. type: " + msgType);
debugUnit(" it's an unknown type: %d", msgType);
break;
}
}
@ -186,5 +476,32 @@ for (const value of buf.values()) {
module.exports = {
FindUnits,
UnitConnection
}
RemoteLogin,
UnitConnection,
LIGHT_CMD_LIGHTS_OFF: 0,
LIGHT_CMD_LIGHTS_ON: 1,
LIGHT_CMD_COLOR_SET: 2,
LIGHT_CMD_COLOR_SYNC: 3,
LIGHT_CMD_COLOR_SWIM: 4,
LIGHT_CMD_COLOR_MODE_PARTY: 5,
LIGHT_CMD_COLOR_MODE_ROMANCE: 6,
LIGHT_CMD_COLOR_MODE_CARIBBEAN: 7,
LIGHT_CMD_COLOR_MODE_AMERICAN: 8,
LIGHT_CMD_COLOR_MODE_SUNSET: 9,
LIGHT_CMD_COLOR_MODE_ROYAL: 10,
LIGHT_CMD_COLOR_SET_SAVE: 11,
LIGHT_CMD_COLOR_SET_RECALL: 12,
LIGHT_CMD_COLOR_BLUE: 13,
LIGHT_CMD_COLOR_GREEN: 14,
LIGHT_CMD_COLOR_RED: 15,
LIGHT_CMD_COLOR_WHITE: 16,
LIGHT_CMD_COLOR_PURPLE: 17,
HEAT_MODE_OFF: 0,
HEAT_MODE_SOLAR: 1,
HEAT_MODE_SOLARPREFERRED: 2,
HEAT_MODE_HEATPUMP: 3,
HEAT_MODE_DONTCHANGE: 4,
PUMP_TYPE_INTELLIFLOVF: 1,
PUMP_TYPE_INTELLIFLOVS: 2,
PUMP_TYPE_INTELLIFLOVSF: 3,
};

29
messages/SLAddClient.js Normal file
View File

@ -0,0 +1,29 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12522;
exports.SLAddClient = class SLAddClient extends SLMessage {
constructor(clientId, senderId) {
if (typeof clientId === 'object') {
var size = clientId.readInt32LE(4) + 8;
super(clientId, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.clientId = clientId;
}
}
encode() {
this.writeInt32LE(0);
this.writeInt32LE(this.clientId);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,30 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12544;
exports.SLAddNewScheduleEvent = class SLAddNewScheduleEvent extends SLMessage {
constructor(buf, scheduleType, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeInt32LE(scheduleType);
}
}
decode() {
super.decode();
this.scheduleId = this.readUInt32LE();
}
static getResponseId() {
return MSG_ID + 1;
}
};

21
messages/SLCancelDelay.js Normal file
View File

@ -0,0 +1,21 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12580;
exports.SLCancelDelay = class SLCancelDelay extends SLMessage {
constructor(senderId) {
super(senderId, MSG_ID);
}
encode() {
this.writeInt32LE(0);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -1,13 +1,32 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 14;
exports.SLChallengeMessage = class SLChallengeMessage extends SLMessage {
constructor() {
super(0, MSG_ID);
constructor(buf) {
var size;
if (buf) {
size = buf.readInt32LE(4) + 8;
}
super(0, MSG_ID, size);
if (buf) {
this._wroteSize = true;
this.writeBuffer(buf, 0);
this.decode();
}
}
decode() {
super.decode();
this.challengeString = this.readSLString();
}
static getResponseId() {
return MSG_ID + 1;
}
}
};

View File

@ -1,17 +1,18 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12592;
exports.SLChemDataMessage = class SLChemDataMessage extends SLMessage {
constructor(buf) {
super(0, MSG_ID);
if (!buf) {
this.writeInt32LE(0); // controller index
constructor(buf, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
this._wroteSize = true;
this.writeBuffer(buf, 0);
super(senderId, MSG_ID);
this.decode();
this.writeInt32LE(0); // controller index
}
}
@ -53,4 +54,4 @@ exports.SLChemDataMessage = class SLChemDataMessage extends SLMessage {
static getResponseId() {
return MSG_ID + 1;
}
}
};

View File

@ -1,18 +1,27 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12532;
exports.SLControllerConfigMessage = class SLControllerConfigMessage extends SLMessage {
constructor(buf) {
super(0, MSG_ID);
if (!buf) {
this.writeInt32LE(0);
this.writeInt32LE(0);
} else {
this._wroteSize = true;
this.writeBuffer(buf, 0);
const CIRCUIT_NAME_VALUE_MAP = [
{name: 'Unused', deviceId: 0},
{name: 'Solar Active', deviceId: 128},
{name: 'Pool or Spa Heater Active', deviceId: 129},
{name: 'Pool Heater Active', deviceId: 130},
{name: 'Spa Heater Active', deviceId: 131},
];
this.decode();
exports.SLControllerConfigMessage = class SLControllerConfigMessage extends SLMessage {
constructor(buf, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeInt32LE(0);
}
}
@ -49,8 +58,8 @@ exports.SLControllerConfigMessage = class SLControllerConfigMessage extends SLMe
colorPos: this.readUInt8(),
colorStagger: this.readUInt8(),
deviceId: this.readUInt8(),
dfaultRt: this.readUInt16LE()
}
dfaultRt: this.readUInt16LE(),
};
this._readOffset += 2;
}
@ -60,11 +69,11 @@ exports.SLControllerConfigMessage = class SLControllerConfigMessage extends SLMe
this.colorArray[i] = {
name: this.readSLString(),
color: {
r: this.readInt32LE() & 0xFF,
g: this.readInt32LE() & 0xFF,
b: this.readInt32LE() & 0xFF
}
}
r: this.readInt32LE() & 0xff,
g: this.readInt32LE() & 0xff,
b: this.readInt32LE() & 0xff,
},
};
}
let pumpCircCount = 8;
@ -80,4 +89,70 @@ exports.SLControllerConfigMessage = class SLControllerConfigMessage extends SLMe
static getResponseId() {
return MSG_ID + 1;
}
}
hasSolar() {
return !!(this.equipFlags & 0x1);
}
hasSolarAsHeatpump() {
return !!(this.equipFlags & 0x2);
}
hasChlorinator() {
return !!(this.equipFlags & 0x4);
}
hasCooling() {
return !!(this.equipFlags & 0x800);
}
hasIntellichem() {
return !!(this.equipFlags & 0x8000);
}
isEasyTouch() {
return this.controllerType === 14 || this.controllerType === 13;
}
isIntelliTouch() {
return this.controllerType !== 14 && this.controllerType !== 13 && this.controllerType !== 10;
}
isEasyTouchLite() {
return this.controllerType === 13 && (this.hwType & 4) !== 0;
}
isDualBody() {
return this.controllerType === 5;
}
isChem2() {
return this.controllerType === 252 && this.hwType === 2;
}
getCircuitByDeviceId(deviceId) {
var deviceArray = this.getCircuitsMap();
for (var i = 0; i < deviceArray.length; i++) {
if (deviceArray[i].deviceId === deviceId) {
return deviceArray[i];
}
}
return null;
}
getCircuitsMap() {
var deviceArray;
if (this.bodyArray) {
deviceArray = this.bodyArray.concat(CIRCUIT_NAME_VALUE_MAP);
} else {
deviceArray = [].concat(CIRCUIT_NAME_VALUE_MAP);
}
return deviceArray;
}
};

View File

@ -0,0 +1,18 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12546;
exports.SLDeleteScheduleEventById = class SLDeleteScheduleEventById extends SLMessage {
constructor(scheduleId, senderId) {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeInt32LE(scheduleId);
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,211 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12566;
exports.SLEquipmentConfigurationMessage = class SLEquipmentConfigurationMessage extends SLMessage {
constructor(buf, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeInt32LE(0);
}
}
decode() {
super.decode();
this.controllerType = this.readUInt8();
this.hardwareType = this.readUInt8();
this.readUInt8();
this.readUInt8();
this.controllerData = this.readInt32LE();
this.versionDataArray = this.readSLArray();
this.speedDataArray = this.readSLArray();
this.valveDataArray = this.readSLArray(); // decodeValveData()
this.remoteDataArray = this.readSLArray();
this.sensorDataArray = this.readSLArray(); // decodeSensorData()
this.delayDataArray = this.readSLArray(); // decodeDelayData()
this.macroDataArray = this.readSLArray();
this.miscDataArray = this.readSLArray(); // decodeMiscData()
this.lightDataArray = this.readSLArray();
this.flowDataArray = this.readSLArray();
this.sgDataArray = this.readSLArray();
this.spaFlowDataArray = this.readSLArray();
}
getVersion() {
if (this.versionDataArray === null || this.versionDataArray.length < 2) {
return 0;
}
return (this.versionDataArray[0] * 1000) + (this.versionDataArray[1]);
}
getSecondariesCount() {
return (this.controllerData & 0x11000000) >> 6;
}
getPumpType(pumpIndex) {
if (typeof (pumpIndex) !== 'number') {
return 0;
}
if (this.flowDataArray === null || this.flowDataArray.length < (pumpIndex + 1) * 45) {
return 0;
}
let pumpType = this.flowDataArray[(45 * pumpIndex) + 2];
if (pumpType <= 3) {
return pumpType;
}
return 0;
}
getCircuitRPMs(pumpIndex, circuitDeviceId) {
if (typeof (pumpIndex) !== 'number' || typeof (circuitDeviceId) !== 'number') {
return 0;
}
if (pumpIndex < 0 || pumpIndex >= 8) {
return 0;
}
if (this.flowDataArray === null || this.flowDataArray.length < (pumpIndex + 1) * 45) {
return 0;
}
for (var i = 0; i < 8; i++) {
let offset = (45 * pumpIndex) + 4 + (i * 2);
if (this.flowDataArray[offset] === circuitDeviceId) {
let upperBits = this.flowDataArray[offset + 1];
let lowerBits = this.flowDataArray[offset + (16 - (i * 2)) + 1 + i];
return (upperBits << 8) + lowerBits;
}
}
return 0;
}
getNumPumps() {
if (this.flowDataArray === null) {
return 0;
}
let numPumps = 0;
for (var i = 0; i < this.flowDataArray.length; i += 45) {
if (this.flowDataArray[i + 2] !== 0) {
numPumps++;
}
}
return numPumps;
}
static getResponseId() {
return MSG_ID + 1;
}
decodeSensorData() {
var sensors = this.sensorDataArray;
this.sensors = {};
this.sensors.poolSolarPresent = this.isBitSet(sensors[0], 1);
this.sensors.spaSolarPresent = this.isBitSet(sensors[0], 4);
this.sensors.thermaFloCoolPresent = this.isBitSet(sensors[1], 1);
this.sensors.solarHeatPumpPresent = this.isBitSet(sensors[2], 4);
this.sensors.thermaFloPresent = this.isBitSet(sensors[2], 5);
}
decodeValveData() {
var secondaries = this.getSecondariesCount();
var isSolarValve0 = false;
var isSolarValve1 = false;
if (!this.sensors) {
this.decodeSensorData();
}
if (this.sensors.poolSolarPresent && !this.sensors.solarHeatPumpPresent) {
isSolarValve0 = true;
}
if (this.isDualBody()) {
if (this.sensors.spaSolarPresent && !this.sensors.thermaFloPresent) {
isSolarValve1 = true;
}
}
var valveArray = [];
for (var loadCenterIndex = 0; loadCenterIndex <= secondaries; loadCenterIndex++) {
var loadCenterValveData = this.valveDataArray[loadCenterIndex];
for (var valveIndex = 0; valveIndex < 5; valveIndex++) {
var valveObject = {};
var isSolarValve = false;
if (loadCenterIndex === 0) {
if (valveIndex === 0 && isSolarValve0) {
isSolarValve = true;
}
if (valveIndex === 1 && isSolarValve1) {
isSolarValve = true;
}
}
if (this.isValvePresent(valveIndex, loadCenterValveData)) {
var valveDataIndex = (loadCenterIndex * 5) + 4 + valveIndex;
var deviceId = this.valveDataArray[valveDataIndex];
if (deviceId === 0) {
// console.log('unused valve, loadCenterIndex = ' + loadCenterIndex + ' valveIndex = ' + valveIndex);
} else if (isSolarValve === true){
// console.log('used by solar');
} else {
valveObject.loadCenterIndex = loadCenterIndex;
valveObject.valveIndex = valveIndex;
valveObject.valveName = String.fromCharCode(65 + valveIndex);
valveObject.loadCenterName = (loadCenterIndex + 1).toString();
valveObject.deviceId = deviceId;
valveArray.push(valveObject);
}
}
}
}
this.valves = valveArray;
}
isValvePresent(valveIndex, loadCenterValveData) {
if (valveIndex < 2) {
return true;
} else {
return this.isBitSet(loadCenterValveData, valveIndex);
}
}
decodeDelayData() {
this.delays = {};
this.delays.poolPumpOnDuringHeaterCooldown = this.isBitSet(this.delayDataArray[0], 0);
this.delays.spaPumpOnDuringHeaterCooldown = this.isBitSet(this.delayDataArray[0], 1);
this.delays.pumpOffDuringValveAction = this.isBitSet(this.delayDataArray[0], 7);
}
decodeMiscData() {
this.misc = {};
this.misc.intelliChem = this.isBitSet(this.miscDataArray[3], 0);
this.misc.spaManualHeat = this.miscDataArray[4] !== 0;
}
isDualBody() {
return this.controllerType === 5;
}
};

View File

@ -0,0 +1,40 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 18003;
exports.SLGetGatewayDataMessage = class SLGetGatewayDataMessage extends SLMessage {
constructor(buf) {
var size;
if (buf && typeof buf === 'object') {
size = buf.readInt32LE(4) + 8;
}
super(0, MSG_ID, size);
if (typeof buf === 'string') {
this.writeSLString(buf);
this.writeSLString(buf);
} else if (buf) {
this._wroteSize = true;
this.writeBuffer(buf, 0);
this.decode();
}
}
decode() {
super.decode();
this.gatewayFound = this.readUInt8() !== 0;
this.licenseOK = this.readUInt8() !== 0;
this.ipAddr = this.readSLString();
this.port = this.readUInt16LE();
this.portOpen = this.readUInt8() !== 0;
this.relayOn = this.readUInt8() !== 0;
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,87 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12534;
const PAYLOAD_MSG_ID = 12502;
exports.SLGetHistoryData = class SLGetHistoryData extends SLMessage {
constructor(buf, fromTime, toTime, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeSLDateTime(fromTime);
this.writeSLDateTime(toTime);
this.writeInt32LE(senderId || 0);
}
}
decode() {
super.decode();
this.airTemps = this.readTimeTempPointsPairs();
this.poolTemps = this.readTimeTempPointsPairs();
this.poolSetPointTemps = this.readTimeTempPointsPairs();
this.spaTemps = this.readTimeTempPointsPairs();
this.spaSetPointTemps = this.readTimeTempPointsPairs();
this.poolRuns = this.readTimeTimePointsPairs();
this.spaRuns = this.readTimeTimePointsPairs();
this.solarRuns = this.readTimeTimePointsPairs();
this.heaterRuns = this.readTimeTimePointsPairs();
this.lightRuns = this.readTimeTimePointsPairs();
}
readTimeTempPointsPairs() {
let retval = [];
// 4 bytes for the length
if (this.length >= this.readOffset + 4) {
let points = this.readInt32LE();
// 16 bytes per time, 4 bytes per temperature
if (this.length >= this.readOffset + (points * (16 + 4))) {
for (let i = 0; i < points; i++) {
let time = this.readSLDateTime();
let temp = this.readInt32LE();
retval.push({
time: time,
temp: temp,
});
}
}
}
return retval;
}
readTimeTimePointsPairs() {
let retval = [];
// 4 bytes for the length
if (this.length >= this.readOffset + 4) {
let points = this.readInt32LE();
// 16 bytes per on time, 16 bytes per off time
if (this.length >= this.readOffset + (points * (16 + 16))) {
for (let i = 0; i < points; i++) {
let onTime = this.readSLDateTime();
let offTime = this.readSLDateTime();
retval.push({
on: onTime,
off: offTime,
});
}
}
}
return retval;
}
static getResponseId() {
return MSG_ID + 1;
}
static getPayloadId() {
return PAYLOAD_MSG_ID;
}
};

View File

@ -0,0 +1,44 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12584;
exports.SLGetPumpStatus = class SLGetPumpStatus extends SLMessage {
constructor(buf, pumpId, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeInt32LE(pumpId);
}
}
decode() {
super.decode();
this.pumpSetting = new Array(8);
this.pumpType = this.readUInt32LE();
this.isRunning = this.readUInt32LE() !== 0; // 0, 1, or 4294967295 (FF FF FF FF)
this.pumpWatts = this.readUInt32LE();
this.pumpRPMs = this.readUInt32LE();
this.pumpUnknown1 = this.readUInt32LE(); // Always 0
this.pumpGPMs = this.readUInt32LE();
this.pumpUnknown2 = this.readUInt32LE(); // Always 255
for (var i = 0; i < 8; i++) {
this.pumpSetting[i] = {};
this.pumpSetting[i].circuitId = this.readUInt32LE();
this.pumpSetting[i].pumpSetPoint = this.readUInt32LE();
this.pumpSetting[i].isRPMs = this.readUInt32LE() !== 0; // 1 for RPMs; 0 for GPMs
}
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,45 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12542;
exports.SLGetScheduleData = class SLGetScheduleData extends SLMessage {
constructor(buf, scheduleType, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeInt32LE(scheduleType);
}
}
decode() {
super.decode();
this.eventCount = this.readUInt32LE();
this.events = new Array(this.eventCount);
for (var i = 0; i < this.events.length; i++) {
this.events[i] = {};
this.events[i].scheduleId = this.readUInt32LE();
this.events[i].circuitId = this.readUInt32LE();
this.events[i].startTime = this.decodeTime(this.readUInt32LE());
this.events[i].stopTime = this.decodeTime(this.readUInt32LE());
this.events[i].dayMask = this.readUInt32LE();
this.events[i].flags = this.readUInt32LE();
this.events[i].heatCmd = this.readUInt32LE();
this.events[i].heatSetPoint = this.readUInt32LE();
this.events[i].days = this.decodeDayMask(this.events[i].dayMask);
}
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,37 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 8110;
exports.SLGetSystemTime = class SLGetSystemTime extends SLMessage {
constructor(buf, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
}
}
decode() {
super.decode();
this.date = this.readSLDateTime();
this.year = this.date.getFullYear();
this.month = this.date.getMonth() + 1; // + 1 is for backward compatibility, SLTime represents months as 1-based
this.dayOfWeek = this.date.getDay(); // should probably be tweaked to adjust what days are 0-6 as SLTime and Javascript start on different days of the week
this.day = this.date.getDate();
this.hour = this.date.getHours();
this.minute = this.date.getMinutes();
this.second = this.date.getSeconds();
this.millisecond = this.date.getMilliseconds();
var adjustForDST = this.readInt32LE();
this.adjustForDST = adjustForDST === 1;
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,30 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12556;
exports.SLLightControl = class SLLightControl extends SLMessage {
constructor(controllerIndex, command, senderId) {
if (typeof controllerIndex === 'object') {
var size = controllerIndex.readInt32LE(4) + 8;
super(controllerIndex, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.controllerIndex = controllerIndex;
this.command = command;
}
}
encode() {
this.writeInt32LE(this.controllerIndex || 0);
this.writeInt32LE(this.command || 0);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -1,18 +1,28 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 27;
exports.SLLoginMessage = class SLLoginMessage extends SLMessage {
constructor() {
constructor(password) {
super(0, MSG_ID);
this.writeInt32LE(348); // schema
this.writeInt32LE(0); // connection type
this.writeSLString('node-screenlogic'); // version
this.writeSLBuffer(Buffer.alloc(16)); // encoded password. empty/unused for local connections
if (!password) {
password = new Array(16);
}
if (password.length > 16) {
password = password.slice(0, 16);
}
this.writeSLArray(password); // encoded password. empty/unused for local connections
this.writeInt32LE(2); // procID
}
static getResponseId() {
return MSG_ID + 1;
}
}
};

View File

@ -1,12 +1,39 @@
'use strict';
const SmartBuffer = require('smart-buffer').SmartBuffer;
exports.SLMessage = class SLMessage extends SmartBuffer {
constructor(senderId, messageId) {
super();
this.writeUInt16LE(senderId);
this.writeUInt16LE(messageId);
const DAY_VALUES = [
['Mon', 0x1 ],
['Tue', 0x2 ],
['Wed', 0x4 ],
['Thu', 0x8 ],
['Fri', 0x10 ],
['Sat', 0x20 ],
['Sun', 0x40 ],
];
this._wroteSize = false;
exports.SLMessage = class SLMessage extends SmartBuffer {
constructor(senderId, messageId, size) {
var options;
if (size) {
options = {
size: size,
};
}
super(options);
if (typeof senderId === 'number' || typeof senderId === 'undefined') {
this.writeUInt16LE(senderId || 0);
this.writeUInt16LE(messageId || 0);
this._wroteSize = false;
} else if (typeof senderId === 'object') {
this._wroteSize = true;
var buffer = senderId;
this.writeBuffer(buffer, 0);
this.decode();
}
}
toBuffer() {
@ -25,17 +52,13 @@ exports.SLMessage = class SLMessage extends SmartBuffer {
writeSLString(str) {
this.writeInt32LE(str.length);
this.writeString(str);
if (str.length % 4 != 0) {
this.skipWrite(4 - (str.length % 4));
}
this.skipWrite(SLMessage.slackForAlignment(str.length));
}
readSLString() {
var len = this.readInt32LE();
var str = this.readString(len);
if (len % 4 != 0) {
this.readOffset += 4 - (len % 4);
}
this.readOffset += SLMessage.slackForAlignment(len);
return str;
}
@ -44,6 +67,29 @@ exports.SLMessage = class SLMessage extends SmartBuffer {
this.writeBuffer(buf);
}
writeSLArray(arr) {
this.writeInt32LE(arr.length);
for (var i = 0; i < arr.length; i++) {
this.writeUInt8(arr[i]);
}
this.skipWrite(SLMessage.slackForAlignment(arr.length));
}
readSLArray() {
var len = this.readInt32LE();
var retval = new Array(len);
for (var i = 0; i < len; i++) {
retval[i] = this.readUInt8();
}
this.readOffset += SLMessage.slackForAlignment(len);
return retval;
}
skipWrite(num) {
if (num > 0) {
this.writeBuffer(Buffer.alloc(num));
@ -57,7 +103,81 @@ exports.SLMessage = class SLMessage extends SmartBuffer {
this.dataLength = this.readInt32LE();
}
encode() {
isBitSet(value, bit) {
return ((value >> bit) & 0x1) === 1;
}
}
decodeTime(rawTime) { // Takes 'rawTime' in mins past midnight and returns military time as a string
var retVal;
retVal = Math.floor(rawTime / 60) * 100 + rawTime % 60;
retVal = String(retVal).padStart(4, '0');
return retVal;
}
encodeTime(stringTime) { // Takes 'stringTime' as military time and returns mins past midnight
return Number(stringTime.substr(0, 2) * 60) + Number(stringTime.substr(2, 2));
}
decodeDayMask(dayMask) {
var days = [];
for (var i = 0; i < 7; i++) {
if (this.isBitSet(dayMask, i)) {
days.push(DAY_VALUES[i][0]);
}
}
return days;
}
encodeDayMask(daysArray) {
var dayMask = 0;
for (var i = 0; i < daysArray.length; i++) {
dayMask += this.getDayValue(daysArray[i]);
}
return dayMask;
}
getDayValue(dayName) {
for (var i = 0; i < DAY_VALUES.length; i++) {
if (DAY_VALUES[i][0] === dayName) {
return DAY_VALUES[i][1];
}
}
return 0;
}
writeSLDateTime(date) {
this.writeInt16LE(date.getFullYear());
this.writeInt16LE(date.getMonth() + 1);
this.writeInt16LE(date.getDay() + 1);
this.writeInt16LE(date.getDate());
this.writeInt16LE(date.getHours());
this.writeInt16LE(date.getMinutes());
this.writeInt16LE(date.getSeconds());
this.writeInt16LE(date.getMilliseconds());
}
readSLDateTime() {
let date = new Date();
date.setFullYear(this.readInt16LE());
date.setMonth(this.readInt16LE() - 1);
this.readInt16LE();
date.setDate(this.readInt16LE());
date.setHours(this.readInt16LE());
date.setMinutes(this.readInt16LE());
date.setSeconds(this.readInt16LE());
date.setMilliseconds(this.readInt16LE());
return date;
}
static slackForAlignment(val) {
return (4 - val % 4) % 4;
}
encode() {}
};

View File

@ -1,20 +1,22 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12526;
const ASYNC_MSG_ID = 12500;
const SPA_CIRCUIT_ID = 500;
const POOL_CIRCUIT_ID = 505;
exports.SLPoolStatusMessage = class SLPoolStatusMessage extends SLMessage {
constructor(buf) {
super(0, MSG_ID);
if (!buf) {
this.writeInt32LE(0);
constructor(buf, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
this._wroteSize = true;
this.writeBuffer(buf, 0);
super(senderId, MSG_ID);
this.decode();
this.writeInt32LE(0);
}
}
@ -60,8 +62,8 @@ exports.SLPoolStatusMessage = class SLPoolStatusMessage extends SLMessage {
colorSet: this.readUInt8(),
colorPos: this.readUInt8(),
colorStagger: this.readUInt8(),
delay: this.readUInt8()
}
delay: this.readUInt8(),
};
}
this.pH = this.readInt32LE() / 100;
@ -85,23 +87,28 @@ exports.SLPoolStatusMessage = class SLPoolStatusMessage extends SLMessage {
return this.ok === 3;
}
isSpaActive() {
circuitData(id) {
for (let i = 0; i < this.circuitArray.length; i++) {
if (this.circuitArray[i].id === SPA_CIRCUIT_ID) {
return this.circuitArray[i].state === 1;
if (this.circuitArray[i].id === id) {
return this.circuitArray[i];
}
}
return undefined;
}
isSpaActive() {
return this.circuitData(SPA_CIRCUIT_ID).state === 1;
}
isPoolActive() {
for (let i = 0; i < this.circuitArray.length; i++) {
if (this.circuitArray[i].id === POOL_CIRCUIT_ID) {
return this.circuitArray[i].state === 1;
}
}
return this.circuitData(POOL_CIRCUIT_ID).state === 1;
}
static getResponseId() {
return MSG_ID + 1;
}
}
static getAsyncResponseId() {
return ASYNC_MSG_ID;
}
};

View File

@ -0,0 +1,29 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12524;
exports.SLRemoveClient = class SLRemoveClient extends SLMessage {
constructor(clientId, senderId) {
if (typeof clientId === 'object') {
var size = clientId.readInt32LE(4) + 8;
super(clientId, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.clientId = clientId;
}
}
encode() {
this.writeInt32LE(0);
this.writeInt32LE(this.clientId);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -1,17 +1,18 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12572;
exports.SLSaltCellConfigMessage = class SLSaltCellConfigMessage extends SLMessage {
constructor(buf) {
super(0, MSG_ID);
if (!buf) {
this.writeInt32LE(0); // controller index
constructor(buf, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
this._wroteSize = true;
this.writeBuffer(buf, 0);
super(senderId, MSG_ID);
this.decode();
this.writeInt32LE(0); // controller index
}
}
@ -30,4 +31,4 @@ exports.SLSaltCellConfigMessage = class SLSaltCellConfigMessage extends SLMessag
static getResponseId() {
return MSG_ID + 1;
}
}
};

View File

@ -0,0 +1,32 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12550;
exports.SLSetCircuitRuntimeById = class SLSetCircuitRuntimeById extends SLMessage {
constructor(circuitId, runTime, senderId) {
if (typeof circuitId === 'object') {
var size = circuitId.readInt32LE(4) + 8;
super(circuitId, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.circuitId = circuitId;
this.runTime = runTime;
}
}
encode() {
this.writeInt32LE(0);
this.writeInt32LE(this.circuitId);
this.writeInt32LE(this.runTime);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -1,14 +1,21 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12530;
exports.SLSetCircuitStateMessage = class SLSetCircuitStateMessage extends SLMessage {
constructor(controllerId, circuitId, circuitState) {
super(0, MSG_ID);
constructor(controllerId, circuitId, circuitState, senderId) {
if (typeof controllerId === 'object') {
var size = controllerId.readInt32LE(4) + 8;
super(controllerId, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.controllerId = controllerId;
this.circuitId = circuitId;
this.circuitState = circuitState;
this.controllerId = controllerId;
this.circuitId = circuitId;
this.circuitState = circuitState;
}
}
encode() {
@ -22,4 +29,4 @@ exports.SLSetCircuitStateMessage = class SLSetCircuitStateMessage extends SLMess
static getResponseId() {
return MSG_ID + 1;
}
}
};

32
messages/SLSetHeatMode.js Normal file
View File

@ -0,0 +1,32 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12538;
exports.SLSetHeatMode = class SLSetHeatMode extends SLMessage {
constructor(controllerIndex, bodyType, heatMode, senderId) {
if (typeof controllerIndex === 'object') {
var size = controllerIndex.readInt32LE(4) + 8;
super(controllerIndex, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.controllerIndex = controllerIndex;
this.bodyType = bodyType;
this.heatMode = heatMode;
}
}
encode() {
this.writeInt32LE(this.controllerIndex || 0);
this.writeInt32LE(this.bodyType || 0);
this.writeInt32LE(this.heatMode || 0);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,32 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12528;
exports.SLSetHeatSetPoint = class SLSetHeatSetPoint extends SLMessage {
constructor(controllerIndex, bodyType, temperature, senderId) {
if (typeof controllerIndex === 'object') {
var size = controllerIndex.readInt32LE(4) + 8;
super(controllerIndex, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.controllerIndex = controllerIndex;
this.bodyType = bodyType;
this.temperature = temperature;
}
}
encode() {
this.writeInt32LE(this.controllerIndex || 0);
this.writeInt32LE(this.bodyType || 0);
this.writeInt32LE(this.temperature || 0);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

41
messages/SLSetPumpFlow.js Normal file
View File

@ -0,0 +1,41 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12586;
exports.SLSetPumpFlow = class SLSetPumpFlow extends SLMessage {
constructor(pumpId, circuitId, setPoint, isRPMs, senderId) {
if (typeof pumpId === 'object') {
var size = pumpId.readInt32LE(4) + 8;
super(pumpId, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.pumpId = pumpId;
this.circuitId = circuitId;
this.setPoint = setPoint;
if (isRPMs === true) {
this.isRPMs = 1;
} else {
this.isRPMs = 0;
}
}
}
encode() {
this.writeInt32LE(0); // Always 0 in my case
this.writeInt32LE(this.pumpId); // presumably pumpId, always 0 in my case
this.writeInt32LE(this.circuitId); // This is indexed to the array of circuits returned in GetPumpStatus
this.writeInt32LE(this.setPoint);
this.writeInt32LE(this.isRPMs); // 0 for GPM, 1 for RPMs
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,34 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12576;
exports.SLSetSaltCellConfigMessage = class SLSetSaltCellConfigMessage extends SLMessage {
constructor(controllerIndex, poolOutput, spaOutput, senderId) {
if (typeof controllerIndex === 'object') {
var size = controllerIndex.readInt32LE(4) + 8;
super(controllerIndex, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.controllerIndex = controllerIndex;
this.poolOutput = poolOutput;
this.spaOutput = spaOutput;
}
}
encode() {
this.writeInt32LE(this.controllerIndex || 0);
this.writeInt32LE(this.poolOutput || 0);
this.writeInt32LE(this.spaOutput || 0);
this.writeInt32LE(0);
this.writeInt32LE(0);
super.encode();
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,31 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 12548;
exports.SLSetScheduleEventById = class SLSetScheduleEventById extends SLMessage {
constructor(buf, scheduleId, circuitId, startTime, stopTime, dayMask, flags, heatCmd, heatSetPoint, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.writeInt32LE(0);
this.writeInt32LE(scheduleId);
this.writeInt32LE(circuitId);
this.writeInt32LE(startTime);
this.writeInt32LE(stopTime);
this.writeInt32LE(dayMask);
this.writeInt32LE(flags);
this.writeInt32LE(heatCmd);
this.writeInt32LE(heatSetPoint);
}
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -0,0 +1,28 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 8112;
exports.SLSetSystemTime = class SLSetSystemTime extends SLMessage {
constructor(buf, date, shouldAdjustForDST, senderId) {
if (buf) {
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
this.date = date;
this.shouldAdjustForDST = shouldAdjustForDST;
}
}
encode() {
this.writeSLDateTime(this.date);
this.writeInt32LE(this.shouldAdjustForDST ? 1 : 0);
}
static getResponseId() {
return MSG_ID + 1;
}
};

View File

@ -1,15 +1,16 @@
'use strict';
const SLMessage = require('./SLMessage.js').SLMessage;
const MSG_ID = 8120;
exports.SLVersionMessage = class SLVersionMessage extends SLMessage {
constructor(buf) {
super(0, MSG_ID);
constructor(buf, senderId) {
if (buf) {
this._wroteSize = true;
this.writeBuffer(buf, 0);
this.decode();
var size = buf.readInt32LE(4) + 8;
super(buf, MSG_ID, size);
} else {
super(senderId, MSG_ID);
}
}
@ -22,4 +23,4 @@ exports.SLVersionMessage = class SLVersionMessage extends SLMessage {
static getResponseId() {
return MSG_ID + 1;
}
}
};

View File

@ -1,8 +1,30 @@
exports.SLPoolStatusMessage = require("./SLPoolStatusMessage.js").SLPoolStatusMessage;
exports.SLControllerConfigMessage = require("./SLControllerConfigMessage.js").SLControllerConfigMessage;
exports.SLChallengeMessage = require("./SLChallengeMessage.js").SLChallengeMessage;
exports.SLLoginMessage = require("./SLLoginMessage.js").SLLoginMessage;
exports.SLChemDataMessage = require("./SLChemDataMessage.js").SLChemDataMessage;
exports.SLSaltCellConfigMessage = require("./SLSaltCellConfigMessage.js").SLSaltCellConfigMessage;
exports.SLVersionMessage = require("./SLVersionMessage.js").SLVersionMessage;
exports.SLSetCircuitStateMessage = require("./SLSetCircuitStateMessage.js").SLSetCircuitStateMessage;
'use strict';
exports.SLPoolStatusMessage = require('./SLPoolStatusMessage.js').SLPoolStatusMessage;
exports.SLControllerConfigMessage = require('./SLControllerConfigMessage.js').SLControllerConfigMessage;
exports.SLChallengeMessage = require('./SLChallengeMessage.js').SLChallengeMessage;
exports.SLLoginMessage = require('./SLLoginMessage.js').SLLoginMessage;
exports.SLChemDataMessage = require('./SLChemDataMessage.js').SLChemDataMessage;
exports.SLSaltCellConfigMessage = require('./SLSaltCellConfigMessage.js').SLSaltCellConfigMessage;
exports.SLVersionMessage = require('./SLVersionMessage.js').SLVersionMessage;
exports.SLSetCircuitStateMessage = require('./SLSetCircuitStateMessage.js').SLSetCircuitStateMessage;
exports.SLGetGatewayDataMessage = require('./SLGetGatewayDataMessage.js').SLGetGatewayDataMessage;
exports.SLSetHeatSetPointMessage = require('./SLSetHeatSetPoint.js').SLSetHeatSetPoint;
exports.SLSetHeatModeMessage = require('./SLSetHeatMode.js').SLSetHeatMode;
exports.SLLightControlMessage = require('./SLLightControl.js').SLLightControl;
exports.SLSetSaltCellConfigMessage = require('./SLSetSaltCellConfigMessage.js').SLSetSaltCellConfigMessage;
exports.SLEquipmentConfigurationMessage =
require('./SLEquipmentConfigurationMessage.js').SLEquipmentConfigurationMessage;
exports.SLGetScheduleData = require('./SLGetScheduleData.js').SLGetScheduleData;
exports.SLAddNewScheduleEvent = require('./SLAddNewScheduleEvent.js').SLAddNewScheduleEvent;
exports.SLDeleteScheduleEventById = require('./SLDeleteScheduleEventById.js').SLDeleteScheduleEventById;
exports.SLSetScheduleEventById = require('./SLSetScheduleEventById.js').SLSetScheduleEventById;
exports.SLSetCircuitRuntimeById = require('./SLSetCircuitRuntimeById.js').SLSetCircuitRuntimeById;
exports.SLGetPumpStatus = require('./SLGetPumpStatus.js').SLGetPumpStatus;
exports.SLSetPumpFlow = require('./SLSetPumpFlow.js').SLSetPumpFlow;
exports.SLCancelDelay = require('./SLCancelDelay.js').SLCancelDelay;
exports.SLAddClient = require('./SLAddClient.js').SLAddClient;
exports.SLRemoveClient = require('./SLRemoveClient.js').SLRemoveClient;
exports.SLGetSystemTime = require('./SLGetSystemTime.js').SLGetSystemTime;
exports.SLSetSystemTime = require('./SLSetSystemTime.js').SLSetSystemTime;
exports.SLGetHistoryData = require('./SLGetHistoryData.js').SLGetHistoryData;

2675
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,28 @@
{
"name": "node-screenlogic",
"description": "Tool for connecting to Pentair ScreenLogic systems on the local network",
"version": "1.1.0",
"main": "index.js",
"license": "MIT",
"repository": "https://github.com/parnic/node-screenlogic.git",
"main": "index.js",
"keywords": [
"pentair",
"pool",
"screenlogic",
"swimmingpool"
],
"dependencies": {
"smart-buffer": "~4.0.1"
}
"name": "node-screenlogic",
"description": "Tool for connecting to Pentair ScreenLogic systems on the local network",
"version": "1.8.0",
"main": "index.js",
"license": "MIT",
"repository": "https://github.com/parnic/node-screenlogic.git",
"keywords": [
"pentair",
"pool",
"screenlogic",
"swimmingpool"
],
"dependencies": {
"debug": "^4.3.2",
"smart-buffer": "^4.2.0"
},
"devDependencies": {
"eslint": "^8.0.0",
"eslint-config-strongloop": "^2.1.0",
"mocha": "^9.1.2"
},
"scripts": {
"test": "mocha test/*.spec.js",
"pretest": "eslint --ignore-path .gitignore .",
"test-slmessage": "mocha test/slmessage.spec.js"
}
}

15
test/find.spec.js Normal file
View File

@ -0,0 +1,15 @@
'use strict';
const ScreenLogic = require('../index');
// you'll need a ScreenLogic-enabled device on your network for this to succeed
describe('Finder', function() {
it('finds a server', function(done) {
let finder = new ScreenLogic.FindUnits();
finder.on('serverFound', server => {
finder.close();
done();
});
finder.search();
});
});

233
test/slmessage.spec.js Normal file
View File

@ -0,0 +1,233 @@
'use strict';
const SLMessage = require('../messages/SLMessage.js').SLMessage;
const assert = require('assert');
function slMessageLen(str) {
// strings have length prefixed on them as an int32 for an additional 4b.
// strings are dword aligned, so if str.length is 21, dword alignment pushes it up to 24
return 4 + str.length + SLMessage.slackForAlignment(str.length);
}
describe('SLMessage utilities', function() {
// message header = senderId, messageId, bodyLen.
// senderId and messageId are int16's, so 2b each. bodyLen is an int32, so 4b. total 8b.
let msgHeaderLen = 8;
it('sets senderId and messageId properly', function() {
{
let msg = new SLMessage(123, 456);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.senderId, 123);
assert.strictEqual(decodedMsg.messageId, 456);
assert.strictEqual(decodedMsg.dataLength, 0);
}
{
let msg = new SLMessage(0, 65534);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.senderId, 0);
assert.strictEqual(decodedMsg.messageId, 65534);
assert.strictEqual(decodedMsg.dataLength, 0);
}
{
let msg = new SLMessage();
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.senderId, 0);
assert.strictEqual(decodedMsg.messageId, 0);
assert.strictEqual(decodedMsg.dataLength, 0);
}
{
let msg = new SLMessage(123);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.senderId, 123);
assert.strictEqual(decodedMsg.messageId, 0);
assert.strictEqual(decodedMsg.dataLength, 0);
}
{
let msg = new SLMessage(0);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.senderId, 0);
assert.strictEqual(decodedMsg.messageId, 0);
assert.strictEqual(decodedMsg.dataLength, 0);
}
});
it('encodes and decodes SLStrings', function() {
{
let msg = new SLMessage();
let testStr = 'this is a test string';
msg.writeSLString(testStr);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.readSLString(), testStr, 'did not receive serialized message properly');
assert.strictEqual(SLMessage.slackForAlignment(testStr.length), 3);
// SLString byte length = 4 + 21 + 3 = 28b
assert.strictEqual(slMessageLen(testStr),
4 + testStr.length + SLMessage.slackForAlignment(testStr.length));
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(testStr), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let testStr = '1';
msg.writeSLString(testStr);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.readSLString(), testStr, 'did not receive serialized message properly');
assert.strictEqual(SLMessage.slackForAlignment(testStr.length), 3);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(testStr), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let testStr = '12';
msg.writeSLString(testStr);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.readSLString(), testStr, 'did not receive serialized message properly');
assert.strictEqual(SLMessage.slackForAlignment(testStr.length), 2);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(testStr), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let testStr = '123';
msg.writeSLString(testStr);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.readSLString(), testStr, 'did not receive serialized message properly');
assert.strictEqual(SLMessage.slackForAlignment(testStr.length), 1);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(testStr), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let testStr = '1234';
msg.writeSLString(testStr);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.strictEqual(decodedMsg.readSLString(), testStr, 'did not receive serialized message properly');
assert.strictEqual(SLMessage.slackForAlignment(testStr.length), 0);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(testStr), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
});
it('encodes and decodes SLArrays', function() {
{
let msg = new SLMessage();
let list = [];
msg.writeSLArray(list);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.deepStrictEqual(decodedMsg.readSLArray(), list);
assert.strictEqual(SLMessage.slackForAlignment(list.length), 0);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(list), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let list = [1];
msg.writeSLArray(list);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.deepStrictEqual(decodedMsg.readSLArray(), list);
assert.strictEqual(SLMessage.slackForAlignment(list.length), 3);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(list), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let list = [1, 2];
msg.writeSLArray(list);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.deepStrictEqual(decodedMsg.readSLArray(), list);
assert.strictEqual(SLMessage.slackForAlignment(list.length), 2);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(list), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let list = [1, 2, 3];
msg.writeSLArray(list);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.deepStrictEqual(decodedMsg.readSLArray(), list);
assert.strictEqual(SLMessage.slackForAlignment(list.length), 1);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(list), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
{
let msg = new SLMessage();
let list = [1, 2, 3, 4];
msg.writeSLArray(list);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.deepStrictEqual(decodedMsg.readSLArray(), list);
assert.strictEqual(SLMessage.slackForAlignment(list.length), 0);
assert.strictEqual(decodedMsg.readOffset, msgHeaderLen + slMessageLen(list), 'read offset was invalid');
assert.strictEqual(decodedMsg.dataLength, decodedMsg.readOffset - 8);
}
});
it('encodes Date as SLTime', function() {
let msg = new SLMessage();
let date = new Date(2021, 8, 6, 22, 8, 5);
msg.writeSLDateTime(date);
let decodedMsg = new SLMessage(msg.toBuffer());
assert.equal(decodedMsg.readUInt16LE(), 2021);
// javascript Date() month is 0-based, ScreenLogic month matches the calendar
assert.equal(decodedMsg.readUInt16LE(), 9);
// ScreenLogic day-of-week starts with Sunday as 1
assert.equal(decodedMsg.readUInt16LE(), 2);
assert.equal(decodedMsg.readUInt16LE(), 6);
assert.equal(decodedMsg.readUInt16LE(), 22);
assert.equal(decodedMsg.readUInt16LE(), 8);
assert.equal(decodedMsg.readUInt16LE(), 5);
assert.equal(decodedMsg.readUInt16LE(), 0);
});
it('decodes SLTime as Date', function() {
let msg = new SLMessage();
let date = new Date(2021, 8, 6, 22, 8, 5);
msg.writeSLDateTime(date);
let decodedMsg = new SLMessage(msg.toBuffer());
let decodedDate = decodedMsg.readSLDateTime();
assert.equal(date.getFullYear(), decodedDate.getFullYear());
assert.equal(date.getMonth(), decodedDate.getMonth());
assert.equal(date.getDate(), decodedDate.getDate());
assert.equal(date.getHours(), decodedDate.getHours());
assert.equal(date.getMinutes(), decodedDate.getMinutes());
assert.equal(date.getSeconds(), decodedDate.getSeconds());
assert.equal(date.getMilliseconds(), decodedDate.getMilliseconds());
});
it('writes the appropriate day of week', function() {
let handler = function(inDate) {
let msg = new SLMessage();
msg.writeSLDateTime(inDate);
let decodedMsg = new SLMessage(msg.toBuffer());
decodedMsg.readUInt16LE();
decodedMsg.readUInt16LE();
return decodedMsg.readUInt16LE();
}
let dow = handler(new Date(2022, 3, 17, 10, 3, 0));
assert.equal(dow, 1);
dow = handler(new Date(2022, 3, 18, 10, 3, 0));
assert.equal(dow, 2);
dow = handler(new Date(2022, 3, 19, 10, 3, 0));
assert.equal(dow, 3);
dow = handler(new Date(2022, 3, 20, 10, 3, 0));
assert.equal(dow, 4);
dow = handler(new Date(2022, 3, 21, 10, 3, 0));
assert.equal(dow, 5);
dow = handler(new Date(2022, 3, 22, 10, 3, 0));
assert.equal(dow, 6);
dow = handler(new Date(2022, 3, 23, 10, 3, 0));
assert.equal(dow, 7);
});
});

82
test/unit.spec.js Normal file
View File

@ -0,0 +1,82 @@
'use strict';
const ScreenLogic = require('../index');
var assert = require('assert');
// you'll need a ScreenLogic-enabled device on your network for this to succeed
describe('Unit', function() {
let unit;
before(function(done) {
let finder = new ScreenLogic.FindUnits();
finder.on('serverFound', server => {
finder.close();
unit = new ScreenLogic.UnitConnection(server);
unit.on('loggedIn', () => {
done();
});
unit.connect();
});
finder.search();
});
after(function() {
unit.close();
});
it('gets pool status', function(done) {
unit.on('poolStatus', status => {
assert.equal(status.senderId, 0);
done();
});
unit.getPoolStatus();
});
it('gets controller config', function(done) {
unit.on('controllerConfig', config => {
assert.equal(config.senderId, 42);
done();
});
unit.getControllerConfig(42);
});
it('gets chemical data', function(done) {
unit.on('chemicalData', chemData => {
assert.equal(chemData.senderId, 123);
done();
});
unit.getChemicalData(123);
});
it('gets salt cell config', function(done) {
unit.on('saltCellConfig', saltConfig => {
assert.equal(saltConfig.senderId, 0);
done();
});
unit.getSaltCellConfig();
});
it('gets version', function(done) {
unit.on('version', version => {
assert.equal(version.senderId, 41239);
done();
});
unit.getVersion(41239);
});
/* uncomment this and the `circuit` stuff above to test setting state
it('sets circuit state', function(done) {
unit.on('circuitStateChanged', () => {
done();
});
unit.setCircuitState(0, circuit.id, circuit.state);
});
*/
});