diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+node_modules
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..53df606
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,10 @@
+{
+ "env": {
+ "node": true,
+ "es2022": true
+ },
+ "extends": "eslint:recommended",
+ "rules": {
+ "indent": ["error", "tab", { "SwitchCase": 1 }]
+ }
+}
diff --git a/.gitignore b/.gitignore
index 6704566..974178a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
+log/
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
@@ -102,3 +103,10 @@ dist
# TernJS port file
.tern-port
+
+test2.js
+test.js
+
+cache/
+
+.DS_Store
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9744ded
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+ "cSpell.words": [
+ "ELEC",
+ "getsocket",
+ "inet",
+ "ipaddr",
+ "ntoa",
+ "precommand"
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 23594c7..c7d1277 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,88 @@
-# switcher-js
-switcher-js is a native nodejs library for controling [switcher smart water heater](https://switcher.co.il/).
-It is a native javascript port of a wonderful python script (can be found [here](https://github.com/NightRang3r/Switcher-V2-Python)) created as a result of the extensive work which has been done by Aviad Golan ([@AviadGolan](https://twitter.com/AviadGolan)) and Shai rod ([@NightRang3r](https://twitter.com/NightRang3r)).
+# switcher-js2
+
+*Fork of [@johnathanvidu JS implementation](https://github.com/johnathanvidu/switcher-js)*
+
+switcher-js is a native nodejs library for controling [Switcher](https://switcher.co.il/) smart home accessories - water heater, sockets, and blinds.
+It is a native javascript port of a wonderful python script (can be found [here](https://github.com/NightRang3r/Switcher-V2-Python)) created as a result of the extensive work which has been done by Aviad Golan ([@AviadGolan](https://twitter.com/AviadGolan)) and Shai rod ([@NightRang3r](https://twitter.com/NightRang3r)).
+
It is a work in progress and there is still a lot of work left to do.
-I built it according to my specific needs and my specific device. If any issue arises, please feel free to open an issue and I'll do my best to help.
-Current supported devices known to work with switcher-js:
-- **Switcher V3** (Switcher touch) - FW **V1.51**
+I built it according to my specific needs and my specific device. If any issue arises, please feel free to open an issue and I'll do my best to help.
+
+Current supported devices known to work with switcher-js:
+
+- **Switcher Lights SL03**
+- **Switcher Lights SL02**
+- **Switcher Lights SL01**
+- **Switcher Runner S12**
+- **Switcher Runner S11**
+- **Switcher Runner Mini**
+- **Switcher Runner**
+- **Switcher V4**
+- **Switcher Mini**
+- **Switcher V3**: (Switcher touch) - Firmware **V1.51**
+- **Switcher V2**: Firmware **3.21** (Based on ESP chipset)
+- **Switcher V2**: Firmware**72.32** (Qualcomm chipset)
## Installation
Use [npm](https://www.npmjs.com/) to install switcher-js.
```bash
-npm install switcher-js
+npm install switcher-js2
```
-## Usage
+## Usage Examples:
```javascript
-const Switcher = require('switcher-js').Switcher;
+const Switcher = require('switcher-js2');
-var switcher = new Switcher('device-id', 'device-ip', 'phone-id', 'device-pass', 'log function');
+var switcher = new Switcher('device-id', 'device-ip', 'log function', 'listen(boolean)', 'device-type');
```
+### Discover
+
To use the auto discover functionallity use:
```javascript
-const Switcher = require('switcher-js').Switcher;
+const Switcher = require('switcher-js2');
-var proxy = Switcher.discover('phone-id', 'device-pass', 'log function');
+var proxy = Switcher.discover('log function', 'identifier(optional)', 'discovery-timeout(optional)');
proxy.on('ready', (switcher) => {
switcher.turn_on(); // switcher is a new initialized instance of Switcher class
});
+
+
+setTimeout(() => {
+ proxy.close(); // optional way to close the discovery (if discovery-timeout is not set)
+}, 10000);
+
```
discover will emit a ready event when auto discovery completed.
-phone-id (optional) - will be defaulted to 0000 if no value provided
-device-pass (optional) - will be defaulted to 00000000 if no value provided
+identifier (optional) - you can provide the Switcher name, IP or device-id to detect specific device.
+discovery-timeout (optional) - set maximum time in seconds to scan for devices.
-Examples:
-```javascript
-const Switcher = require('switcher-js').Switcher;
-var switcher = new Switcher('device-id', 'device-ip', 'phone-id', 'device-pass', 'log function');
+### Control
-switcher.on('state', (state) => { // state is the new switcher state
-
+```javascript
+const Switcher = require('switcher-js2');
+
+var switcher = new Switcher('device-id', 'device-ip', 'log function', 'listen', 'device-type');
+// set listen to true if you want to listen for status messages
+
+switcher.on('status', (status) => { // status broadcast message - only works when listen=true
+ console.log(status)
+ /* response:
+ {
+ power: 1,
+ remaining_seconds: 591,
+ default_shutdown_seconds: 5400,
+ power_consumption: 2447 // in watts
+ }
+ */
+});
+switcher.on('state', (state) => { // state is the new switcher state
+ console.log(state) // 1
});
switcher.on('error', (error) => {
@@ -52,18 +91,85 @@ switcher.on('error', (error) => {
switcher.turn_on(); // turns switcher on
switcher.turn_on(15); // turns switcher on for 15 minutes
switcher.turn_off(); // turns switcher off
+switcher.set_default_shutdown(3600) // set the default auto shutdown to 1 hour (must be between 3600 and 86340)
+switcher.status(status => { // get status
+ console.log(status);
+});
switcher.close(); // closes any dangling connections safely
```
-switcher-js exposes two states for convenience
+### Control Runner Devices (blinds)
```javascript
-const switcher = require('switcher-js');
+const Switcher = require('switcher-js2');
+
+var runner = new Switcher('device-id', 'device-ip', 'log function', 'listen', 'runner');
+// set 'device-type' to 'runner' if you want to control the runner devices
+
+runner.on('status', (status) => { // status broadcast message - only works when listen=true
+ console.log(status)
+ /* response:
+ {
+ position: 80,
+ direction: 'STOP'
+ }
+ */
+});
+runner.on('position', (pos) => { // position is the new switcher position
+ console.log(pos) // 100
+});
+switcher.on('error', (error) => {
+
+});
+
+switcher.set_position(80); // Set blinds position to 80%
+
+switcher.stop_runner() // stop the blinds
+
+switcher.close(); // closes any dangling connections safely
+```
+
+### Listen
+
+Global listen functionality that listens to a single or multiple switcher devices for status messages.
+
+To use the listen functionallity use:
+```javascript
+const Switcher = require('switcher-js2');
+
+var proxy = Switcher.listen('log function', 'identifier(optional)');
+
+proxy.on('message', (message) => {
+ console.log(message)
+ /* response:
+ {
+ device_id: 'e3a845',
+ device_ip: '10.0.0.1',
+ name: 'Boiler',
+ type: 'v4'
+ state: {
+ power: 1,
+ remaining_seconds: 591,
+ default_shutdown_seconds: 5400,
+ power_consumption: 2447 // in watts
+ }
+ }
+ */
+});
+
+proxy.close(); // close the listener socket
-switcher.ON = 0
-switcher.OFF = 1
```
+proxy will emit a message event every time it receives a message from a switcher device.
+
+identifier (optional) - you can provide the Switcher name, IP or device-id to filter specific device messages.
+
+## Multiple Connections
+
+Don't use Discover, Listen and Switcher with (listen=true) at the same time as it will return error since this socket is being used.
+If you want to listen to multiple devices, use the global listen function to get all statuses, and use the switcher instance without the listen capability.
+
## Contributing
Pull requests are more than welcome. For major changes, please open an issue first to discuss what you would like to change.
Even coding tips and standards are welcome, I have very limited experience with javascript, so there's a lot of things I don't know are cleaner or more standarized in the industry.
diff --git a/index.js b/index.js
index 168c9c2..f1ea6bf 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,4 @@
const switcher = require('./src/switcher');
-module.exports = {
- Switcher: switcher.Switcher
-}
\ No newline at end of file
+module.exports = switcher.Switcher
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index bd66476..e68a224 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,21 +1,1120 @@
{
- "name": "switcher-js",
- "version": "0.0.3",
- "lockfileVersion": 1,
+ "name": "switcher-js2",
+ "version": "1.6.8",
+ "lockfileVersion": 3,
"requires": true,
- "dependencies": {
- "long": {
+ "packages": {
+ "": {
+ "name": "switcher-js2",
+ "version": "1.6.8",
+ "license": "MIT",
+ "dependencies": {
+ "adm-zip": "^0.5.17",
+ "python-struct": "^1.1.3"
+ },
+ "devDependencies": {
+ "eslint": "^9.39.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/adm-zip": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz",
+ "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
- "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
},
- "python-struct": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/python-struct/-/python-struct-1.1.2.tgz",
- "integrity": "sha512-LJOsUEBLcfV5813MY6ilE1vsz0Yoan+ttdYi+vkgu89nJICmFlMnjQd4Qjzs3dGPKIsC2D1w6CQM1yFsL9mXtA==",
- "requires": {
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/python-struct": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/python-struct/-/python-struct-1.1.3.tgz",
+ "integrity": "sha512-UsI/mNvk25jRpGKYI38Nfbv84z48oiIWwG67DLVvjRhy8B/0aIK+5Ju5WOHgw/o9rnEmbAS00v4rgKFQeC332Q==",
+ "license": "MIT",
+ "dependencies": {
"long": "^4.0.0"
}
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/package.json b/package.json
index 67333f2..2249631 100644
--- a/package.json
+++ b/package.json
@@ -1,27 +1,33 @@
{
- "name": "switcher-js",
- "version": "0.0.3",
- "description": "switcher smart water heater api",
+ "name": "switcher-js2",
+ "version": "1.8.0",
+ "description": "switcher smart accessories api (fork of @johnathanvidu)",
"main": "index.js",
+ "engines": {
+ "node": ">=18.0.0"
+ },
"dependencies": {
- "python-struct": "^1.1.2"
+ "adm-zip": "^0.5.17",
+ "python-struct": "^1.1.3"
+ },
+ "devDependencies": {
+ "eslint": "^9.39.4"
},
- "devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/johnathanvidu/switcher-js.git"
+ "url": "git+https://github.com/nitaybz/switcher-js.git"
},
"keywords": [
"switcher",
"switcher-js"
],
- "author": "Johnathan Viduchinsky",
+ "author": "nitaybz",
"license": "MIT",
"bugs": {
- "url": "https://github.com/johnathanvidu/switcher-js/issues"
+ "url": "https://github.com/nitaybz/switcher-js/issues"
},
- "homepage": "https://github.com/johnathanvidu/switcher-js#readme"
+ "homepage": "https://github.com/nitaybz/switcher-js#readme"
}
diff --git a/src/crc.js b/src/crc.js
index 72718ca..0ee4ed6 100644
--- a/src/crc.js
+++ b/src/crc.js
@@ -1,60 +1,60 @@
// Generated by `./pycrc.py --algorithm=table-driven --model=ccitt --generate=c`
// prettier-ignore
let TABLE = [
- 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
- 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
- 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
- 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
- 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
- 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
- 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
- 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
- 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
- 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
- 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
- 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
- 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
- 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
- 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
- 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
- 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
- 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
- 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
- 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
- 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
- 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
- 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
- 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
- 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
- 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
- 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
- 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
- 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
- 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
- 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
- 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
+ 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
+ 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
+ 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
+ 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
+ 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
+ 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
+ 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
+ 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
+ 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
+ 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
+ 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
+ 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
+ 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
+ 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+ 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
+ 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
+ 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
+ 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
+ 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
+ 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
+ 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
+ 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
];
-defineCrc = function(model, calc) {
- const fn = (buf, previous) => calc(buf, previous) >>> 0;
- fn.signed = calc;
- fn.unsigned = fn;
- fn.model = model;
+const defineCrc = function(model, calc) {
+ const fn = (buf, previous) => calc(buf, previous) >>> 0;
+ fn.signed = calc;
+ fn.unsigned = fn;
+ fn.model = model;
- return fn;
- }
+ return fn;
+}
if (typeof Int32Array !== 'undefined') TABLE = new Int32Array(TABLE);
const crc16ccitt = defineCrc('ccitt', function(buf, previous) {
- let crc = typeof previous !== 'undefined' ? ~~previous : 0xffff;
- for (let index = 0; index < buf.length; index++) {
- const byte = buf[index];
- crc = (TABLE[((crc >> 8) ^ byte) & 0xff] ^ (crc << 8)) & 0xffff;
- }
+ let crc = typeof previous !== 'undefined' ? ~~previous : 0xffff;
+ for (let index = 0; index < buf.length; index++) {
+ const byte = buf[index];
+ crc = (TABLE[((crc >> 8) ^ byte) & 0xff] ^ (crc << 8)) & 0xffff;
+ }
- return crc;
+ return crc;
});
exports.crc16ccitt = crc16ccitt;
\ No newline at end of file
diff --git a/src/switcher.js b/src/switcher.js
index 63b15b4..cbff631 100644
--- a/src/switcher.js
+++ b/src/switcher.js
@@ -1,290 +1,1139 @@
+/* eslint-disable no-async-promise-executor */
"use strict";
-
+const { he } = require('./tok');
const net = require('net');
const dgram = require('dgram');
const struct = require('python-struct');
const EventEmitter = require('events').EventEmitter;
+var AdmZip = require("adm-zip");
+var zip = new AdmZip(__dirname + "/t.zip");
const crc16ccitt = require('./crc').crc16ccitt;
-
+const SwitcherUDPMessage = require('./udp')
const P_SESSION = '00000000';
const P_KEY = '00000000000000000000000000000000';
const STATUS_EVENT = 'status';
+const MESSAGE_EVENT = 'message'
const READY_EVENT = 'ready';
const ERROR_EVENT = 'error';
const STATE_CHANGED_EVENT = 'state';
+const DURATION_CHANGED_EVENT = 'duration';
+const BREEZE_CAPABILITIES_EVENT = 'capabilities'
+
+
+const SWITCHER_UDP_IP = "0.0.0.0";
+const SWITCHER_UDP_PORT = 20002;
+const SWITCHER_UDP_PORT2 = 20003;
+const SWITCHER_UDP_PORT3 = 10002;
+const SWITCHER_UDP_PORT4 = 10003;
+const LISTENING_PORTS = [SWITCHER_UDP_PORT, SWITCHER_UDP_PORT2, SWITCHER_UDP_PORT3, SWITCHER_UDP_PORT4]
-const SWITCHER_UDP_IP = "0.0.0.0"
-const SWITCHER_UDP_PORT = 20002
+const SWITCHER_TCP_PORT = 9957;
+const SWITCHER_TCP_PORT2 = 10000;
+const OLD_TCP_GROUP = ['power_plug', 'v2_qca', 'v2_esp', 'v3', 'v4', 'mini'];
+const TOKEN_REQUIRED_TYPES = ['s11', 's12', 'sl01', 'sl02', 'sl03', 'slmini01', 'slmini02', 'heater'];
const OFF = 0;
const ON = 1;
+const SEPARATED_SWING_REMOTES = [
+ "ELEC7022",
+ "ZM079055",
+ "ZM079065",
+ "ZM079049",
+ "ZM079065",
+]
+
+const breeze_dictionary = {
+ modes: {
+ 'aa': 'AUTO',
+ 'ad': 'DRY',
+ 'aw': 'FAN',
+ 'ar': 'COOL',
+ 'ah': 'HEAT'
+
+ },
+ fan_levels: {
+ 'f0': 'AUTO',
+ 'f1': 'LOW',
+ 'f2': 'MEDIUM',
+ 'f3': 'HIGH',
+ }
+}
class ConnectionError extends Error {
- constructor(ip, port) {
- super('connection error: failed to connect to switcher on ip: ${ip}:${port}. please make sure it is turned on and available.');
- this.ip = ip;
- this.port = port;
- }
+ constructor(ip, port) {
+ super(`connection error: failed to connect to switcher on ip: ${ip}:${port}. please make sure it is turned on and available.`);
+ this.ip = ip;
+ this.port = port;
+ }
}
-class SwitcherUDPMessage {
- constructor(message_buffer) {
- this.data_str = message_buffer.toString();
- this.data_hex = message_buffer.toString('hex');
- }
-
- static is_valid(message_buffer) {
- return !(message_buffer.toString('hex').substr(0, 4) != 'fef0' &&
- message_buffer.byteLength() != 165);
- }
-
- extract_ip_addr() {
- var ip_addr_section = this.data_hex.substr(152, 8);
- var ip_addr_int = parseInt(
- ip_addr_section.substr(0, 2) +
- ip_addr_section.substr(2, 2) +
- ip_addr_section.substr(4, 2) +
- ip_addr_section.substr(6, 2), 16);
- return this.inet_ntoa(ip_addr_int);
- }
-
- extract_device_name() {
- return this.data_str.substr(41, 32);
- }
-
- extract_device_id() {
- return this.data_hex.substr(36, 6);
- }
-
- extract_switch_state() {
- return this.data_hex.substr(266, 4) == '0000' ? OFF : ON;
- }
-
- extract_shutdown_remaining_seconds() {
- var time_left_section = this.data_hex.substr(294, 8);
- return parseInt(
- time_left_section.substr(6, 2) +
- time_left_section.substr(4, 2) +
- time_left_section.substr(2, 2) +
- time_left_section.substr(0, 2), 16);
- }
-
- inet_ntoa(num) { // extract to utils https://stackoverflow.com/a/21613691
- var a = ((num >> 24) & 0xFF) >>> 0;
- var b = ((num >> 16) & 0xFF) >>> 0;
- var c = ((num >> 8) & 0xFF) >>> 0;
- var d = (num & 0xFF) >>> 0;
- return(a + "." + b + "." + c + "." + d);
- }
-}
-
-
-class Switcher extends EventEmitter {
- SWITCHER_PORT = 9957;
-
- constructor(device_id, switcher_ip, phone_id, device_pass, log) {
- super();
- this.device_id = device_id;
- this.switcher_ip = switcher_ip;
- this.phone_id = phone_id || '0000';
- this.device_pass = device_pass || '00000000';
- this.log = log;
- this.p_session = null;
- this.socket = null;
- this.status_socket = this._hijack_status_report();
- }
-
- static discover(phone_id, device_pass, log) {
- var proxy = new EventEmitter.EventEmitter();
- var socket = dgram.createSocket('udp4', (raw_msg, rinfo) => {
- var ipaddr = rinfo.address;
- if (!SwitcherUDPMessage.is_valid(raw_msg)) {
- return; // ignoring - not a switcher broadcast message
- }
- var udp_message = new SwitcherUDPMessage(raw_msg);
- var device_id = udp_message.extract_device_id();
- proxy.emit(READY_EVENT, new Switcher(device_id, ipaddr, phone_id, device_pass, log));
- socket.close();
- });
- socket.on('error', (error) => {
- proxy.emit(ERROR_EVENT, error);
- });
- socket.bind(SWITCHER_UDP_PORT, SWITCHER_UDP_IP);
- return proxy;
- }
-
- turn_off() {
- var off_command = OFF + '00' + '00000000';
- this._run_power_command(off_command);
- }
-
- turn_on(duration=0) {
- var on_command = ON +'00' + this._timer_value(duration);
- this._run_power_command(on_command);
- }
-
- async status(callback) { // refactor
- var p_session = await this._login();
- var data = "fef0300002320103" + p_session + "340001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id + "00";
- data = this._crc_sign_full_packet_com_key(data, P_KEY);
- var socket = await this._getsocket();
- socket.write(Buffer.from(data, 'hex'));
- socket.once('data', (data) => {
- var device_name = data.toString().substr(40, 32);
- var state_hex = data.toString('hex').substr(150, 4);
- var b = data.toString('hex').substr(178, 8);
- var remaining_seconds = parseInt(b.substr(6, 2) + b.substr(4, 2) + b.substr(2, 2) + b.substr(0, 2), 16);
- var state = state_hex == '0000' ? OFF : ON;
- callback({
- name: device_name,
- state: state,
- remaining_seconds: remaining_seconds,
- });
- });
- }
-
- close() {
- this.log.debug('closing sockets');
- if (this.socket && !this.socket.destroyed) {
- this.socket.destroy();
- this.log.debug('main socket is closed');
- }
- if (this.status_socket && !this.status_socket.destroyed) {
- this.status_socket.close();
- this.log.debug('status socket is closed');
- }
- }
-
- async _getsocket() {
- if (this.socket && !this.socket.destroyed) {
- return await this.socket;
- }
- try {
- var socket = await this._connect(this.SWITCHER_PORT, this.switcher_ip);
- socket.on('error', (error) => {
- this.log.debug('gloabal error event:', error);
- });
- socket.on('close', (had_error) => {
- this.log.debug('gloabal close event:', had_error);
- });
- this.socket = socket;
- return socket;
- }
- catch(error) {
- this.socket = null;
- this.emit(ERROR_EVENT, new ConnectionError(this.switcher_ip, this.SWITCHER_PORT));
- throw error;
- }
- }
-
- _connect(port, ip) {
- return new Promise((resolve, reject) => {
- var socket = net.connect(port, ip);
- socket.setKeepAlive(true);
- socket.once('ready', () => {
- this.log.debug('successful connection, socket was created');
- resolve(socket);
- });
- socket.once('close', (had_error) => {
- this.log.debug('connection closed, had error:', had_error)
- reject(had_error);
- });
- socket.once('error', (error) => {
- this.log.debug('connection rejected, error:', error)
- reject(error);
- });
- });
- }
-
- _hijack_status_report() {
- var socket = dgram.createSocket('udp4', (raw_msg, rinfo) => {
- if (!SwitcherUDPMessage.is_valid(raw_msg)) {
- return; // ignoring - not a switcher broadcast message
- }
- var udp_message = new SwitcherUDPMessage(raw_msg);
- this.emit(STATUS_EVENT, {
- name: udp_message.extract_device_name(),
- state: udp_message.extract_switch_state(),
- remaining_seconds: udp_message.extract_shutdown_remaining_seconds()
- })
- });
- socket.on('error', (error) => {
- this.emit(ERROR_EVENT, new Error("status report failed. error: " + error.message)); // hoping this will keep the original stack trace
- });
- socket.bind(SWITCHER_UDP_PORT, SWITCHER_UDP_IP);
- return socket;
- }
-
- async _login() {
- if (this.p_session) return this.p_session;
- try {
- this.p_session = await new Promise(async (resolve, reject) => {
- var data = "fef052000232a100" + P_SESSION + "340001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe1c00" +
- this.phone_id + "0000" + this.device_pass + "00000000000000000000000000000000000000000000000000000000";
- data = this._crc_sign_full_packet_com_key(data, P_KEY);
- this.log.debug("login...");
- var socket = await this._getsocket();
- socket.write(Buffer.from(data, 'hex'));
- socket.once('data', (data) => {
- var result_session = data.toString('hex').substr(16, 8)
- // todo: make sure result_session exists
- this.log.debug('recieved session id: ' + result_session);
- resolve(result_session); // returning _p_session after a successful login
- });
- this.socket.once('error', (error) => {
- reject(error);
- });
- });
- }
- catch (error) {
- this.log('login failed due to an error', error);
- this.emit(ERROR_EVENT, new Error('login failed due to an error: ${error.message}'));
- }
- return this.p_session;
- }
-
- async _run_power_command(command_type) {
- var p_session = await this._login();
- var data = "fef05d0002320102" + p_session + "340001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id +
- "00" + this.phone_id + "0000" + this.device_pass + "000000000000000000000000000000000000000000000000000000000106000" + command_type;
- data = this._crc_sign_full_packet_com_key(data, P_KEY);
- this.log.debug('sending ' + Object.keys({OFF, ON})[command_type.substr(0, 1)] + ' command');
- var socket = await this._getsocket();
- socket.write(Buffer.from(data, 'hex'));
- socket.once('data', (data) => {
- this.emit(STATE_CHANGED_EVENT, command_type.substr(0, 1)); // todo: add old state and new state
- });
- }
-
- _get_time_stamp() {
- var time_in_seconds = Math.round(new Date().getTime() / 1000);
- return struct.pack('I', crc16ccitt(Buffer.from(p_data, 'hex'), 0x1021)).toString('hex');
- p_data = p_data + crc.substr(6, 2) + crc.substr(4, 2);
- crc = crc.substr(6, 2) + crc.substr(4, 2) + Buffer.from(p_key).toString('hex');
- crc = struct.pack('>I', crc16ccitt(Buffer.from(crc, 'hex'), 0x1021)).toString('hex');
- p_data = p_data + crc.substr(6, 2) + crc.substr(4, 2);
- return p_data
- }
+
+class Switcher extends EventEmitter {
+ constructor(device_id, switcher_ip, log, listen, device_type, remote, token, key) {
+ super();
+ this.device_id = device_id;
+ this.switcher_ip = switcher_ip;
+ this.device_key = key;
+ this.device_type = device_type || 'unknown';
+ this.phone_id = '0000';
+ if (token)
+ this.token = he(token);
+ this.device_pass = '00000000';
+ this.newType = !OLD_TCP_GROUP.includes(device_type)
+ this.isBreeze = device_type && device_type === 'breeze'
+ this.isHeater = device_type && device_type === 'heater'
+ this.SWITCHER_PORT = this.newType ? SWITCHER_TCP_PORT2 : SWITCHER_TCP_PORT;
+ this.log = log;
+ this.p_session = null;
+ this.socket = null;
+ this.log(`Switcher init: device_id=${this.device_id} ip=${this.switcher_ip} type=${this.device_type} newType=${this.newType} TCP_PORT=${this.SWITCHER_PORT}`);
+ if (listen)
+ this.status_socket = this._hijack_status_report();
+ if (device_type === 'breeze')
+ this._get_breeze_remote(remote)
+ .then(remote => this.breeze_remote = remote)
+ }
+
+ static discover(log, identifier, discovery_timeout) {
+ var proxy = new EventEmitter.EventEmitter();
+ var timeout = null
+ const sockets = []
+
+ LISTENING_PORTS.forEach(switcher_port => {
+ var socket = dgram.createSocket('udp4', (raw_msg, rinfo) => {
+ var ipaddr = rinfo.address;
+ if (!SwitcherUDPMessage.is_valid(raw_msg)) {
+ return; // ignoring - not a switcher broadcast message
+ }
+ var udp_message = new SwitcherUDPMessage(raw_msg);
+ var device_id = udp_message.extract_device_id();
+ var device_name = udp_message.extract_device_name();
+ var device_type = udp_message.extract_type();
+ var device_key = udp_message.extract_device_key();
+ if (device_type === 'breeze')
+ var remote = udp_message.extract_remote();
+ if (identifier && identifier !== device_id && identifier !== device_name && identifier !== ipaddr) {
+ log(`Found ${device_name} (${ipaddr}) - Not the device we're looking for!`);
+ return;
+ }
+
+ // log(`Found ${device_name} (${ipaddr})!`);
+ proxy.emit(READY_EVENT, new Switcher(device_id, ipaddr, log, false, device_type, remote, device_key));
+ clearTimeout(timeout);
+ socket.close();
+ socket = null;
+
+ });
+ socket.on('error', (error) => {
+ proxy.emit(ERROR_EVENT, error);
+ clearTimeout(timeout);
+ socket.close();
+ socket = null;
+ });
+ socket.bind(switcher_port, SWITCHER_UDP_IP);
+ sockets.push(socket)
+
+ })
+
+ if (discovery_timeout) {
+ timeout = setTimeout(() => {
+ log(`stopping discovery, closing sockets`);
+ sockets.forEach(socket => {
+ socket.close();
+ socket = null;
+ })
+ }, discovery_timeout * 1000);
+ }
+
+ proxy.close = () => {
+ log('closing discover socket');
+ sockets.forEach(socket => {
+ socket.close();
+ })
+ }
+ return proxy;
+ }
+
+ static listen(log, identifier) {
+ var proxy = new EventEmitter.EventEmitter();
+
+ const sockets = []
+
+ LISTENING_PORTS.forEach(switcher_port => {
+ var socket = dgram.createSocket('udp4', (raw_msg, rinfo) => {
+ var ipaddr = rinfo.address;
+ if (!SwitcherUDPMessage.is_valid(raw_msg)) {
+ return; // ignoring - not a switcher broadcast message
+ }
+ var udp_message = new SwitcherUDPMessage(raw_msg);
+ var device_id = udp_message.extract_device_id();
+ var device_name = udp_message.extract_device_name();
+ if (identifier && identifier !== device_id && identifier !== device_name && identifier !== ipaddr) {
+ log(`Found ${device_name} (${ipaddr}) - Not the device we're looking for!`);
+ return;
+ }
+
+ var device_type = udp_message.extract_type();
+ if (['power_plug', 'v2_qca', 'v2_esp', 'v3', 'v4', 'mini', 'on_wall'].includes(device_type)) {
+ proxy.emit(MESSAGE_EVENT, {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ device_key: udp_message.extract_device_key(),
+ type: udp_message.extract_type(),
+ state: {
+ power: udp_message.extract_switch_state(),
+ remaining_seconds: udp_message.extract_shutdown_remaining_seconds(),
+ default_shutdown_seconds: udp_message.extract_default_shutdown_seconds(),
+ power_consumption: udp_message.extract_power_consumption()
+ }
+ });
+ } else if (device_type === 'heater') {
+ // Switcher Heater (031f) uses a different broadcast layout than the older
+ // boiler family. Default-shutdown is not in the UDP broadcast on this model;
+ // it is only available via the TCP state response.
+ proxy.emit(MESSAGE_EVENT, {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ device_key: udp_message.extract_device_key(),
+ type: device_type,
+ state: {
+ power: udp_message.extract_heater_state(),
+ remaining_seconds: udp_message.extract_heater_remaining_seconds(),
+ default_shutdown_seconds: 0,
+ power_consumption: udp_message.extract_heater_power_consumption()
+ }
+ });
+ } else if (device_type === 'breeze') {
+ proxy.emit(MESSAGE_EVENT, {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ remote: udp_message.extract_remote(),
+ type: device_type,
+ state: {
+ power: udp_message.extract_ac_power(),
+ current_temp: udp_message.extract_current_temp(),
+ target_temp: udp_message.extract_target_temp(),
+ mode: udp_message.extract_ac_mode(),
+ fan_level: udp_message.extract_fan_level(),
+ swing: udp_message.extract_swing()
+ }
+ })
+ }
+ else if (device_type.includes('runner'))
+ proxy.emit(MESSAGE_EVENT, {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ type: device_type,
+ state: {
+ position: udp_message.extract_position(),
+ direction: udp_message.extract_direction(),
+ child_lock: udp_message.extract_child_lock()
+ }
+ });
+
+ else if (device_type === 's11')
+ proxy.emit(MESSAGE_EVENT, {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ type: device_type,
+ state: {
+ light1_power: udp_message.extract_light(1),
+ light2_power: udp_message.extract_light(2),
+ runner3_position: udp_message.extract_position(3),
+ runner3_direction: udp_message.extract_direction(3),
+ runner3_child_lock: udp_message.extract_child_lock(3)
+ }
+ });
+
+ else if (device_type === 's12')
+ proxy.emit(MESSAGE_EVENT, {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ type: device_type,
+ state: {
+ light1_power: udp_message.extract_light(1),
+ runner2_position: udp_message.extract_position(2),
+ runner2_direction: udp_message.extract_direction(2),
+ runner2_child_lock: udp_message.extract_child_lock(2),
+ runner3_position: udp_message.extract_position(3),
+ runner3_direction: udp_message.extract_direction(3),
+ runner3_child_lock: udp_message.extract_child_lock(3)
+ }
+ });
+
+ else if (/^sl(mini)?0\d$/.test(device_type)) {
+ const light_status = {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ type: device_type,
+ state: {
+ light1_power: udp_message.extract_light(1)
+ }
+ }
+ if (parseInt(device_type.replace(/\D/g, '')) > 1)
+ light_status.state.light2_power = udp_message.extract_light(2)
+ if (parseInt(device_type.replace(/\D/g, '')) > 2)
+ light_status.state.light3_power = udp_message.extract_light(3)
+ proxy.emit(MESSAGE_EVENT, light_status);
+ }
+
+ else
+ proxy.emit(MESSAGE_EVENT, {
+ device_id: device_id,
+ device_ip: ipaddr,
+ name: device_name,
+ type: device_type,
+ data_hex: udp_message.data_hex,
+ data_str: udp_message.data_str
+ });
+
+ })
+
+ socket.on('error', (error) => {
+ proxy.emit(ERROR_EVENT, error);
+ socket.close();
+ socket = null;
+ });
+ socket.bind(switcher_port, SWITCHER_UDP_IP);
+ sockets.push(socket)
+ })
+
+ proxy.close = () => {
+ log('closing listen socket');
+ sockets.forEach(socket => {
+ socket.close();
+ })
+ }
+ return proxy;
+ }
+
+ update_device_key(key) {
+ this.log('device key updated with', key)
+ this.device_key = key
+ }
+
+ turn_off() {
+ if (this.isHeater) {
+ this._run_heater_power_command(OFF, 0);
+ return;
+ }
+ var off_command = OFF + '00' + '00000000';
+ this._run_power_command(off_command);
+ }
+
+ turn_on(duration = 0) {
+ if (this.isHeater) {
+ this._run_heater_power_command(ON, duration);
+ return;
+ }
+ var on_command = ON + '00' + this._timer_value(duration);
+ this._run_power_command(on_command);
+ }
+
+ stop_runner(index = 0) {
+ this.log(`Sending stop command`)
+ let command = '0000'
+ command = index ? `0${index}` + command : command
+ this._run_general_command(command, '3702');
+ }
+
+ set_child_lock(lock = false, index = 0) {
+ this.log(`Sending child lock command: ${lock}`)
+ let command = lock ? '01' : '00'
+ command = index ? `0${index}` + command : command
+ this._run_general_command(command, '3707');
+ }
+
+ set_light(power = false, index = 0) {
+ this.log(`Sending light power command: ${power}`)
+ let command = power ? '01' : '00'
+ command = index ? `0${index}` + command : command
+ this._run_general_command(command, '370a');
+ }
+
+ set_position(pos = 0, index = 0) {
+ this.log(`Sending position command: ${pos}%`)
+ let command = this._get_hex_pos(pos)
+ command = index ? `0${index}` + command : command
+ this._run_general_command(command);
+ }
+
+
+ is_breeze_on() {
+ return this.status()
+ .then(status => {
+ return status.power === 'ON'
+ })
+ }
+
+ set_separated_swing_command(state) {
+ const key = state ? 'FUN_d1' : 'FUN_d0'
+
+ this.log(`sending separated swing command: ${JSON.stringify(state)} (${key})`)
+
+ // find command in IRWaveList
+ const IRCommand = this.remote_set.IRWaveList.find(wave => wave.Key === key)
+
+ if (!IRCommand) {
+ this.log(`ERROR: Wrong IR Command (${key})! Can't send separated swing command !!!`)
+ return
+ }
+ let command = `${IRCommand.Para}|${IRCommand.HexCode}`
+ command = "00000000" + this._ascii_to_hex(command)
+ this._run_general_command(command);
+ }
+
+ set_breeze_command(state) {
+ this.is_breeze_on()
+ .then(isOn => {
+ if (state.power === 'OFF' && !isOn) {
+ // Do nothing
+ this.log('already off')
+ return null
+ }
+ let command = ''
+ let IRCommand, commandKey
+ if (state.power === 'OFF' && isOn) {
+ // turn OFF
+ this.log('turning off breeze')
+ commandKey = 'off'
+ } else if (state.power === 'ON' && !isOn && this.remote_set.OnOffType) {
+ // turn ON and set command
+ this.log('sending on command with state:' + JSON.stringify(state))
+ commandKey = 'on_' + this._get_breeze_command_key(state)
+ } else {
+ // only set command
+ this.log('sending change state command:' + JSON.stringify(state))
+ commandKey = this._get_breeze_command_key(state)
+ }
+
+ // find command in IRWaveList
+ IRCommand = this.remote_set.IRWaveList.find(wave => wave.Key === commandKey)
+
+ // if not found, find similar command that includes some of the params (e.g "on_ad" instead of "on_ad_f0")
+ if (!IRCommand)
+ IRCommand = this.remote_set.IRWaveList.find(wave => commandKey.includes(wave.Key))
+
+ if (!IRCommand) {
+ this.log(`ERROR: Wrong IR Command (${commandKey})! Can't send command !!!`)
+ return
+ }
+ command = `${IRCommand.Para}|${IRCommand.HexCode}`
+ command = "00000000" + this._ascii_to_hex(command)
+ this._run_general_command(command);
+
+ if (this.breeze_remote.separated_swing && state.swing === 'ON') {
+ setTimeout(this.set_separated_swing_command, 1000, true)
+ }
+
+ })
+ }
+
+ async set_default_shutdown(duration = 3600) {
+ try {
+ var auto_close = this._set_default_shutdown(duration)
+ let p_session = await this._login();
+ let data = "fef05b0002320102" + p_session + "340001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id +
+ "00" + this.phone_id + "0000" + this.device_pass + "00000000000000000000000000000000000000000000000000000000040400" + auto_close;
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ this.log(`sending default_shutdown command | ${duration} seconds`);
+ var socket = await this._getsocket();
+ socket.write(Buffer.from(data, 'hex'));
+ socket.once('data', () => {
+ this.emit(DURATION_CHANGED_EVENT, duration); // todo: add old state and new state
+ });
+ } catch (err) {
+ this.log('set_default_shutdown failed:', err && err.message ? err.message : err)
+ this.emit(ERROR_EVENT, err)
+ }
+ }
+
+ async status() { // refactor
+ return new Promise(async (resolve, reject) => {
+ let data, p_session
+ if (this.isHeater) {
+ if (!this.token) {
+ return reject(new Error('Switcher Heater requires a token. Get yours at https://switcher.co.il/GetKey and pass it to the constructor.'));
+ }
+ // Heater requires the two-step token login before any state query.
+ p_session = await this._login3();
+ data = "fef0300003050103" + p_session + "390001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id + "00"
+ } else if (this.newType) {
+ p_session = await this._login2();
+ data = "fef0300003050103" + p_session + "390001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id + "00"
+ } else {
+ p_session = await this._login();
+ data = "fef0300002320103" + p_session + "340001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id + "00";
+ }
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ var socket = await this._getsocket();
+ socket.write(Buffer.from(data, 'hex'));
+ socket.once('data', (data) => {
+ try {
+ // var device_name = data.toString().substr(40, 32).replace(/\0/g, '');
+ if (this.isHeater) {
+ // Heater TCP state response offsets, verified against aioswitcher's
+ // StateMessageParser (messages.py) get_heater_* methods.
+ const hex = data.toString('hex');
+ const state = hex.substr(152, 2) == '01' ? ON : OFF;
+ let section = hex.substr(168, 8);
+ const power_consumption = parseInt(section.substr(2, 2) + section.substr(0, 2), 16);
+ section = hex.substr(192, 8);
+ const default_shutdown_seconds = parseInt(section.substr(6, 2) + section.substr(4, 2) + section.substr(2, 2) + section.substr(0, 2), 16);
+ section = hex.substr(208, 8);
+ const remaining_seconds = parseInt(section.substr(6, 2) + section.substr(4, 2) + section.substr(2, 2) + section.substr(0, 2), 16);
+ resolve({
+ device_id: this.device_id,
+ power: state,
+ remaining_seconds: remaining_seconds,
+ default_shutdown_seconds: default_shutdown_seconds,
+ power_consumption: power_consumption
+ });
+ return;
+ }
+ if (this.isBreeze) {
+ const data_hex = data.toString('hex')
+ const state = {
+ device_id: this.device_id,
+ remote: data.toString().substr(84, 8).replace(/\0/g, ''),
+ current_temp: parseInt(data_hex.substr(154, 2) + data_hex.substr(152, 2), 16) / 10,
+ power: data_hex.substr(156, 2) == '00' ? 'OFF' : 'ON',
+ target_temp: parseInt(data_hex.substr(160, 2), 16),
+ mode: SwitcherUDPMessage.get_breeze_mode(data_hex.substr(158, 2)),
+ fan_level: SwitcherUDPMessage.get_breeze_fan_level(data_hex.substr(162, 1)),
+ swing: data_hex.substr(162, 1) == '0' ? 'OFF' : 'ON'
+ }
+ resolve(state);
+ } else {
+ var state_hex = data.toString('hex').substr(150, 4);
+ var state = state_hex == '0000' ? OFF : ON;
+ var b = data.toString('hex').substr(178, 8);
+ var remaining_seconds = parseInt(b.substr(6, 2) + b.substr(4, 2) + b.substr(2, 2) + b.substr(0, 2), 16);
+ b = data.toString('hex').substr(194, 8);
+ var default_shutdown_seconds = parseInt(b.substr(6, 2) + b.substr(4, 2) + b.substr(2, 2) + b.substr(0, 2), 16);
+ b = data.toString('hex').substr(154, 4);
+ var power_consumption = parseInt(b.substr(2, 2) + b.substr(0, 2), 16);
+ resolve({
+ device_id: this.device_id,
+ power: state,
+ remaining_seconds: remaining_seconds,
+ default_shutdown_seconds: default_shutdown_seconds,
+ power_consumption: power_consumption
+ });
+ }
+ } catch (error) {
+ this.log('connection rejected, error:', error)
+ reject(error);
+ }
+ });
+ })
+ }
+
+ close() {
+ if (this.socket && !this.socket.destroyed) {
+ this.log('closing sockets');
+ this.socket.destroy();
+ this.log('main socket is closed');
+ }
+ if (this.status_socket && !this.status_socket.destroyed) {
+ this.log('closing sockets');
+ this.status_socket.close();
+ this.log('status socket is closed');
+ }
+ }
+
+ async _getsocket() {
+ if (this.socket && !this.socket.destroyed) {
+ return await this.socket;
+ }
+ try {
+ var socket = await this._connect(this.SWITCHER_PORT, this.switcher_ip);
+ socket.on('error', (error) => {
+ this.log('global error event:', error && error.message ? error.message : error);
+ if (this.socket === socket) {
+ this.socket = null;
+ this.p_session = null;
+ }
+ });
+ socket.on('close', (had_error) => {
+ this.log('global close event:', had_error);
+ if (this.socket === socket) {
+ this.socket = null;
+ this.p_session = null;
+ }
+ });
+ this.socket = socket;
+ return socket;
+ }
+ catch (error) {
+ this.socket = null;
+ this.p_session = null;
+ this.emit(ERROR_EVENT, new ConnectionError(this.switcher_ip, this.SWITCHER_PORT));
+ throw error;
+ }
+ }
+
+ _connect(port, ip) {
+ return new Promise((resolve, reject) => {
+ this.log(`opening TCP connection to ${ip}:${port}`);
+ var socket = net.connect(port, ip);
+ // 30s keepalive so dead idle connections (overnight, WiFi reassoc, etc.)
+ // are detected within a useful window instead of the OS default ~2 hours.
+ socket.setKeepAlive(true, 30000);
+ socket.once('ready', () => {
+ this.log(`TCP connection ready (${ip}:${port})`);
+ resolve(socket);
+ });
+ socket.once('close', (had_error) => {
+ this.log(`connection closed (${ip}:${port}), had error:`, had_error)
+ reject(had_error);
+ });
+ socket.once('error', (error) => {
+ this.log(`connection rejected (${ip}:${port}), error:`, error && error.message ? error.message : error)
+ reject(error);
+ });
+ });
+ }
+
+ _hijack_status_report() {
+ var socket = dgram.createSocket('udp4', (raw_msg, /* rinfo */) => {
+ if (!SwitcherUDPMessage.is_valid(raw_msg)) {
+ return; // ignoring - not a switcher broadcast message
+ }
+ // var ipaddr = rinfo.address;
+ var udp_message = new SwitcherUDPMessage(raw_msg);
+
+ var device_id = udp_message.extract_device_id()
+ if (device_id === this.device_id) {
+ if (!this.newType)
+ this.emit(STATUS_EVENT, {
+ device_key: udp_message.extract_device_key(),
+ power: udp_message.extract_switch_state(),
+ remaining_seconds: udp_message.extract_shutdown_remaining_seconds(),
+ default_shutdown_seconds: udp_message.extract_default_shutdown_seconds(),
+ power_consumption: udp_message.extract_power_consumption()
+ })
+ else if (this.isHeater)
+ this.emit(STATUS_EVENT, {
+ device_key: udp_message.extract_device_key(),
+ power: udp_message.extract_heater_state(),
+ remaining_seconds: udp_message.extract_heater_remaining_seconds(),
+ default_shutdown_seconds: 0,
+ power_consumption: udp_message.extract_heater_power_consumption()
+ })
+ else if (this.isBreeze)
+ this.emit(STATUS_EVENT, {
+ power: udp_message.extract_ac_power(),
+ current_temp: udp_message.extract_current_temp(),
+ target_temp: udp_message.extract_target_temp(),
+ mode: udp_message.extract_ac_mode(),
+ fan_level: udp_message.extract_fan_level(),
+ swing: udp_message.extract_swing()
+ })
+ else if (this.device_type === 's11')
+ this.emit(STATUS_EVENT, {
+ light1_power: udp_message.extract_light(1),
+ light2_power: udp_message.extract_light(2),
+ runner3_position: udp_message.extract_position(3),
+ runner3_direction: udp_message.extract_direction(3),
+ runner3_child_lock: udp_message.extract_child_lock(3)
+ });
+
+ else if (this.device_type === 's12')
+ this.emit(STATUS_EVENT, {
+ light1_power: udp_message.extract_light(1),
+ runner2_position: udp_message.extract_position(2),
+ runner2_direction: udp_message.extract_direction(2),
+ runner2_child_lock: udp_message.extract_child_lock(2),
+ runner3_position: udp_message.extract_position(3),
+ runner3_direction: udp_message.extract_direction(3),
+ runner3_child_lock: udp_message.extract_child_lock(3)
+ });
+
+ else if (/^sl(mini)?0\d$/.test(this.device_type)) {
+ const light_status = {
+ light1_power: udp_message.extract_light(1)
+ }
+ if (parseInt(this.device_type.replace(/\D/g, '')) > 1)
+ light_status.light2_power = udp_message.extract_light(2)
+ if (parseInt(this.device_type.replace(/\D/g, '')) > 2)
+ light_status.light3_power = udp_message.extract_light(3)
+ this.emit(STATUS_EVENT, light_status);
+ }
+
+ else // if (device_type.includes('runner'))
+ this.emit(STATUS_EVENT, {
+ position: udp_message.extract_position(),
+ direction: udp_message.extract_direction(),
+ child_lock: udp_message.extract_child_lock()
+ })
+ }
+ });
+ socket.on('error', (error) => {
+ this.emit(ERROR_EVENT, new Error("status report failed. error: " + error.message)); // hoping this will keep the original stack trace
+ });
+ socket.bind((!this.newType ? SWITCHER_UDP_PORT : SWITCHER_UDP_PORT2), SWITCHER_UDP_IP);
+ return socket;
+ }
+
+ async _get_breeze_remote(remote) {
+ try {
+ this.remote_set = await this._get_remote_set(remote)
+ } catch (err) {
+ this.log(`Can't get remote set for ${remote} !`)
+ this.log(err.message || err.stack || err)
+ return
+ }
+
+ const capabilities = {
+ remote,
+ modes: [],
+ fan_levels: [],
+ swing: false,
+ min_temp: 100,
+ max_temp: 0
+ }
+
+ if (!this.remote_set.IRWaveList || !this.remote_set.IRWaveList.length) {
+ this.log(`Wrong Remote, can't find commands!`)
+ this.log('Remote Set:')
+ this.log(this.remote_set)
+ return
+ }
+
+ for (const wave of this.remote_set.IRWaveList) {
+ const key = wave.Key
+ // add modes
+ const newMode = breeze_dictionary.modes[key.substr(0, 2)]
+ if (newMode && !capabilities.modes.includes(newMode))
+ capabilities.modes.push(newMode)
+
+ // add fan levels
+ const newFanLevel = key.match(/f\d/) ? breeze_dictionary.fan_levels[key.match(/f\d/)[0]] : null
+ if (newFanLevel && !capabilities.fan_levels.includes(newFanLevel))
+ capabilities.fan_levels.push(newFanLevel)
+
+ // add min/max temperatures
+ const newTemp = key.substr(2, 2) ? parseInt(key.substr(2, 2)) : null
+ if (newTemp && newTemp > capabilities.max_temp)
+ capabilities.max_temp = newTemp
+ if (newTemp && newTemp < capabilities.min_temp)
+ capabilities.min_temp = newTemp
+
+ // swing
+ const swingAvailable = key.match(/d1/)
+ if (swingAvailable)
+ capabilities.swing = true
+
+ if (SEPARATED_SWING_REMOTES.includes(remote)) {
+ capabilities.swing = true
+ capabilities.separated_swing = true
+ }
+ }
+
+ this.emit(BREEZE_CAPABILITIES_EVENT, capabilities)
+ this.log('remote capabilities:' + JSON.stringify(capabilities))
+ return capabilities
+ }
+
+ async _get_remote_set(remote) {
+ return new Promise(async (resolve, reject) => {
+
+ const zipEntry = zip.getEntries()[0]
+ let IRWaves = zipEntry.getData().toString("utf8")
+ IRWaves = JSON.parse(IRWaves)
+
+ const remote_waves = IRWaves.find(remote_set => remote_set.IRSetID === remote)
+ if (remote_waves)
+ resolve(remote_waves)
+ else
+ reject(new Error(`Can't find remote ${remote}`))
+ })
+ }
+
+ async _login() {
+ if (this.p_session) return this.p_session;
+ try {
+ this.p_session = await new Promise(async (resolve, reject) => {
+ let data = "fef052000232a100" + P_SESSION + "340001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_key + "00" +
+ this.phone_id + "0000" + this.device_pass + "00000000000000000000000000000000000000000000000000000000";
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ this.log("_login (old TCP, port 9957) starting...");
+ try {
+ var socket = await this._getsocket();
+ } catch (err) {
+ reject(err)
+ return
+ }
+ this.log('sending data')
+ this.log(data)
+ socket.write(Buffer.from(data, 'hex'));
+ this.log('_login: waiting for response from device...');
+ socket.once('data', (data) => {
+ var result_session = data.toString('hex').substr(16, 8)
+ this.log('received login data:')
+ this.log(data.toString('hex'))
+ // todo: make sure result_session exists
+ this.log('received session id: ' + result_session);
+ resolve(result_session); // returning _p_session after a successful login
+ });
+ this.socket.once('error', (error) => {
+ reject(error);
+ });
+ });
+ }
+ catch (error) {
+ this.log('login failed due to an error', error);
+ this.emit(ERROR_EVENT, new Error(`login failed due to an error: ${error.message}`));
+ }
+ return this.p_session;
+ }
+
+ async _login2() {
+ if (this.p_session) return this.p_session;
+ try {
+ this.p_session = await new Promise(async (resolve, reject) => {
+ let data = "fef030000305a600" + P_SESSION + "ff0301000000" + this.phone_id + "00000000" + this._get_time_stamp() + "00000000000000000000f0fe" +
+ this.device_id + "00";
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ this.log("_login2 (new TCP, port 10000) starting...");
+ try {
+ var socket = await this._getsocket();
+ } catch (err) {
+ reject(err)
+ return
+ }
+ this.log('sending data')
+ this.log(data)
+ socket.write(Buffer.from(data, 'hex'));
+ this.log('_login2: waiting for response from device...');
+ socket.once('data', (data) => {
+ var result_session = data.toString('hex').substr(16, 8)
+ this.log('received login data:')
+ this.log(data.toString('hex'))
+ // todo: make sure result_session exists
+ this.log('received session id: ' + result_session);
+ resolve(result_session); // returning _p_session after a successful login
+ });
+ this.socket.once('error', (error) => {
+ reject(error);
+ });
+ });
+ }
+ catch (error) {
+ this.log('login failed due to an error', error);
+ this.emit(ERROR_EVENT, new Error(`login failed due to an error: ${error.message}`));
+ }
+ return this.p_session;
+ }
+
+ async _login3() {
+ if (this.p_session) return this.p_session;
+ try {
+ this.p_session = await new Promise(async (resolve, reject) => {
+ let data1 = "fef030000305a600" + P_SESSION + "ff0301000000" +"00"+ this.token + "00" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id + "00";
+ data1 = this._crc_sign_full_packet_com_key(data1, P_KEY);
+ let data2 = "fef053000305a100" + P_SESSION + "f50301000000" + this.device_id+ "000000" + this._get_time_stamp() + "00000000000000000000f0fe" +"0000" + this.token + "000000000000000000000000000000000000000000000000000000000000000001"
+ data2 = this._crc_sign_full_packet_com_key(data2, P_KEY);
+ this.log("_login3 (token auth, port 10000) step 1 starting...");
+ try {
+ var socket = await this._getsocket();
+ } catch (err) {
+ reject(err)
+ return
+ }
+ this.log('sending data1')
+ this.log(data1)
+ socket.write(Buffer.from(data1, 'hex'));
+ socket.once('data', (data1) => {
+ var result_session = data1.toString('hex').substr(16, 8)
+ this.log('received login data1:')
+ this.log(data1.toString('hex'))
+ // todo: make sure result_session exists
+ this.log('received session id: ' + result_session);
+ // send second packet after receiving response to first packet
+ this.log('sending data2')
+ this.log(data2)
+ socket.write(Buffer.from(data2, 'hex'));
+ socket.once('data', (data2) => {
+ this.log('received login data2:')
+ this.log(data2.toString('hex'))
+ this.log('received session id: ' + result_session);
+ resolve(result_session); // returning _p_session after a successful login
+ });
+ });
+ this.socket.once('error', (error) => {
+ reject(error);
+ });
+ });
+ }
+ catch (error) {
+ this.log('login failed due to an error', error);
+ this.emit(ERROR_EVENT, new Error(`login failed due to an error: ${error.message}`));
+ }
+ return this.p_session;
+ }
+
+
+
+ async _run_power_command(command_type) {
+ const attempt = async () => {
+ let p_session = await this._login();
+ if (!p_session)
+ throw new Error('login returned no session')
+ let data = "fef05d0002320102" + p_session + "340001" + "000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id +
+ "00" + this.phone_id + "0000" + this.device_pass + "000000000000000000000000000000000000000000000000000000000106000" + command_type;
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ this.log('sending ' + Object.keys({ OFF, ON })[command_type.substr(0, 1)] + ' command');
+ let socket = await this._getsocket();
+ this.log('sending data:')
+ this.log(data)
+ socket.write(Buffer.from(data, 'hex'));
+ this.log('_run_power_command: waiting for response from device...');
+ socket.once('data', (data) => {
+ this.log('data received:')
+ this.log(data.toString('hex'))
+ this.emit(STATE_CHANGED_EVENT, command_type.substr(0, 1));
+ });
+ }
+
+ try {
+ await attempt()
+ } catch (err) {
+ this.log('power command first attempt failed, retrying once:', err && err.message ? err.message : err)
+ this._reset_connection_state()
+ try {
+ await attempt()
+ } catch (err2) {
+ this.log('power command failed after retry:', err2 && err2.message ? err2.message : err2)
+ this.emit(ERROR_EVENT, err2)
+ }
+ }
+ }
+
+ async _run_heater_power_command(command_value, duration = 0) {
+ if (!this.token) {
+ const err = new Error('Switcher Heater requires a token. Get yours at https://switcher.co.il/GetKey and pass it to the constructor.');
+ this.log(err.message);
+ this.emit(ERROR_EVENT, err);
+ return;
+ }
+
+ const attempt = async () => {
+ const p_session = await this._login3();
+ this.p_session = null;
+ if (!p_session)
+ throw new Error('login3 returned no session');
+
+ const timer = duration > 0 ? this._timer_value(duration) : '00000000';
+ // Heater control hex_pos is "0" + command_value + timer (32-bit LE seconds),
+ // total 10 hex chars / 5 bytes. Verified against aioswitcher control_device.
+ const hex_pos = '0' + command_value + timer;
+ const precommand = '3723'; // CONTROL_DEVICE_PRECOMMAND
+
+ // GENERAL_TOKEN_COMMAND uses a hard-coded session id of "00000000" because
+ // the auth credential is the token in the body, not the login session id.
+ // Mirrors aioswitcher packets.GENERAL_TOKEN_COMMAND verbatim.
+ let data = "fef0000003050102" + P_SESSION + "000000" + "000000000000000000"
+ + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id
+ + "00" + this.token + this.device_pass
+ + "000000000000000000000000000000000000000000000000000000"
+ + precommand + hex_pos;
+ data = this._set_message_length(data);
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ const socket = await this._getsocket();
+ this.log(`sending heater ${command_value === ON ? 'ON' : 'OFF'} command (duration=${duration}m)`);
+ socket.write(Buffer.from(data, 'hex'));
+ socket.once('data', (resp) => {
+ this.log('heater command response: ' + resp.toString('hex'));
+ this.emit(STATE_CHANGED_EVENT, String(command_value));
+ });
+ };
+
+ try {
+ await attempt();
+ } catch (err) {
+ this.log('heater power command first attempt failed, retrying once:', err && err.message ? err.message : err);
+ this._reset_connection_state();
+ try {
+ await attempt();
+ } catch (err2) {
+ this.log('heater power command failed after retry:', err2 && err2.message ? err2.message : err2);
+ this.emit(ERROR_EVENT, err2);
+ }
+ }
+ }
+
+ async _run_general_command(command, precommand = "3701") {
+ const attempt = async () => {
+ let data, p_session
+ if(this.token && (this.device_type === 's11' || this.device_type === 's12' || /^sl(mini)?0\d$/.test(this.device_type))){
+ p_session = await this._login3();
+ this.p_session = null;
+ if (!p_session)
+ throw new Error('login3 returned no session')
+ data = "fef0000003050102" + p_session + "000000" + "000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id +
+ "00" + this.token + this.device_pass + "000000000000000000000000000000000000000000000000000000" + precommand + this._get_command_length(command + "00000000") + command + "00000000"
+ } else {
+ p_session = await this._login2();
+ this.p_session = null;
+ if (!p_session)
+ throw new Error('login2 returned no session')
+ data = "fef0000003050102" + p_session + "000000" + "000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id +
+ "00" + this.phone_id + "0000" + this.device_pass + "000000000000000000000000000000000000000000000000000000" + precommand + this._get_command_length(command) + command
+ }
+
+ data = this._set_message_length(data)
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ var socket = await this._getsocket();
+ this.log('sending data:')
+ this.log(data)
+ socket.write(Buffer.from(data, 'hex'));
+ socket.once('data', (data) => {
+ this.log('data received:')
+ this.log(data.toString('hex'))
+ });
+ }
+
+ try {
+ await attempt()
+ } catch (err) {
+ this.log('general command first attempt failed, retrying once:', err && err.message ? err.message : err)
+ this._reset_connection_state()
+ try {
+ await attempt()
+ } catch (err2) {
+ this.log('general command failed after retry:', err2 && err2.message ? err2.message : err2)
+ this.emit(ERROR_EVENT, err2)
+ }
+ }
+ }
+
+ _reset_connection_state() {
+ if (this.socket && !this.socket.destroyed) {
+ try { this.socket.destroy() } catch (e) { /* ignore */ }
+ }
+ this.socket = null;
+ this.p_session = null;
+ }
+
+ _get_time_stamp() {
+ var time_in_seconds = Math.round(new Date().getTime() / 1000);
+ return struct.pack(' 86340) {
+ this.log('Value can\'t be more than 23 hours and 59 minutes, setting to 86340')
+ seconds = 86340
+ } else return struct.pack(' breeze_dictionary.modes[key] === state.mode)
+
+ // add temp & sanitize
+ if (['COOL', 'HEAT'].includes(state.mode)) {
+ if (state.target_temp > this.breeze_remote.max_temp)
+ command += this.breeze_remote.max_temp
+ else if (state.target_temp < this.breeze_remote.min_temp)
+ command += this.breeze_remote.min_temp
+ else command += state.target_temp || this.breeze_remote.min_temp
+ }
+
+ // add fan level
+ if (this.breeze_remote.fan_levels && this.breeze_remote.fan_levels.includes(state.fan_level))
+ command += `_${Object.keys(breeze_dictionary.fan_levels).find(key => breeze_dictionary.fan_levels[key] === state.fan_level)}`
+
+ // add swing
+ if (!this.breeze_remote.separated_swing && this.breeze_remote.swing && state.swing === 'ON')
+ command += `_d1`
+
+ return command
+ }
+ _get_udp_for_remote() {
+ return new Promise(async (resolve, reject) => {
+ let p_session = await this._login2();
+ let data = "fef0300003050103" + p_session + "390001000000000000000000" + this._get_time_stamp() + "00000000000000000000f0fe" + this.device_id + "00"
+ data = this._crc_sign_full_packet_com_key(data, P_KEY);
+ var socket = await this._getsocket();
+ socket.write(Buffer.from(data, 'hex'));
+ socket.once('data', (data) => {
+ resolve(data.toString('hex'));
+ });
+ socket.on('error', (error) => {
+ reject(error)
+ });
+ })
+ }
+
+ _crc_sign_full_packet_com_key(p_data, p_key) {
+ var crc = struct.pack('>I', crc16ccitt(Buffer.from(p_data, 'hex'), 0x1021)).toString('hex');
+ p_data = p_data + crc.substr(6, 2) + crc.substr(4, 2);
+ crc = crc.substr(6, 2) + crc.substr(4, 2) + Buffer.from(p_key).toString('hex');
+ crc = struct.pack('>I', crc16ccitt(Buffer.from(crc, 'hex'), 0x1021)).toString('hex');
+ p_data = p_data + crc.substr(6, 2) + crc.substr(4, 2);
+ return p_data
+ }
+
+ _set_message_length(data) {
+ let hex = Number(Buffer.byteLength(Buffer.from(data + "00000000", "hex"))).toString(16)
+ hex = hex.padStart(4, "0")
+ hex = hex.substr(2, 2) + hex.substr(0, 2);
+ return "fef0" + hex + data.substr(8)
+ }
+
+ _get_command_length(command) {
+ let hex = Number(Buffer.byteLength(Buffer.from(command, "hex"))).toString(16)
+ hex = hex.padStart(4, "0")
+ hex = hex.substr(2, 2) + hex.substr(0, 2);
+ return hex
+ }
+
+
+ _ascii_to_hex(str) {
+ const arr1 = [];
+ for (let n = 0, l = str.length; n < l; n++) {
+ const hex = Number(str.charCodeAt(n)).toString(16);
+ arr1.push(hex);
+ }
+ return arr1.join('');
+ }
+
}
module.exports = {
- Switcher: Switcher,
- ConnectionError: ConnectionError,
- ON: ON,
- OFF, OFF
+ Switcher: Switcher,
+ ConnectionError: ConnectionError
}
\ No newline at end of file
diff --git a/src/t.zip b/src/t.zip
new file mode 100644
index 0000000..4bf3a42
Binary files /dev/null and b/src/t.zip differ
diff --git a/src/tok.js b/src/tok.js
new file mode 100644
index 0000000..e8a684a
--- /dev/null
+++ b/src/tok.js
@@ -0,0 +1,6 @@
+/* eslint-disable no-empty */
+/* eslint-disable no-extra-boolean-cast */
+/* eslint-disable no-cond-assign */
+/* eslint-disable no-func-assign */
+/* eslint-disable no-unused-vars */
+function _0x3048a8($,x,e,t,n){return _0x15e3($-294,n)}!function($,x){function e($,x,e,t,n){return _0x15e3($- -516,t)}let t=$();function n($,x,e,t,n){return _0x15e3(t-240,$)}function _($,x,e,t,n){return _0x15e3(t-509,$)}function c($,x,e,t,n){return _0x15e3(n- -518,$)}function W($,x,e,t,n){return _0x15e3(t-700,$)}for(;;)try{let r=parseInt(_0x15e3(533,"xMgc"))/1*(parseInt(_0x15e3(513,"94I]"))/2)+parseInt(_0x15e3(511,"Fm5l"))/3+parseInt(_0x15e3(521,"DJl#"))/4+-parseInt(_0x15e3(494,"MxEP"))/5*(-parseInt(_0x15e3(516,"ZxS3"))/6)+parseInt(_0x15e3(586,"mQ4y"))/7*(-parseInt(_0x15e3(562,"ThZ5"))/8)+parseInt(_0x15e3(579,")^hW"))/9*(-parseInt(_0x15e3(527,"WbEY"))/10)+parseInt(_0x15e3(542,"(n55"))/11;if(232482===r)break;t.push(t.shift())}catch(o){t.push(t.shift())}}(_0x5867,232482);const _0x26a811=function(){let $=!0;return function(x,e){let t=$?function(){function $($,x,e,t,n){return _0x15e3($- -370,x)}if(e){let t=e[_0x15e3(517,"4af0")](x,arguments);return e=null,t}}:function(){};return $=!1,t}}(),_0x4ba45e=_0x26a811(this,function(){function $($,x,e,t,n){return _0x15e3(n-39,$)}function x($,x,e,t,n){return _0x15e3(x-93,n)}let e={};function t($,x,e,t,n){return _0x15e3($- -45,e)}function n($,x,e,t,n){return _0x15e3(n- -810,e)}var _=1464,c=1501,W="o*Z[",r=1440,o=1451,u=1538,f=1550,d="tQ3^",i=1580,a=1548;e[_0x15e3(_-932,W)]=_0x15e3(545,"1H^r")+_0x15e3(u-932,d)+"+$";let l=e;function h($,x,e,t,n){return _0x15e3($-932,e)}return _0x4ba45e[_0x15e3(563,"lJNN")+_0x15e3(528,"Jbr[")]()[_0x15e3(602,"3%Mm")+"h"](l[_0x15e3(583,"I38b")])[_0x15e3(523,")^hW")+_0x15e3(591,"zlTG")]()[_0x15e3(512,"EqW&")+_0x15e3(495,"4qq7")+"r"](_0x4ba45e)[_0x15e3(522,"4qq7")+"h"](l[_0x15e3(487,"MxEP")])});function _0x54fbc7($,x,e,t,n){return _0x15e3(x-952,t)}_0x4ba45e();const _0x4bd28d=function(){let $=!0;return function(x,e){let t=$?function(){function $($,x,e,t,n){return _0x15e3(x-317,$)}if(e){let t=e[_0x15e3(574,"w3LS")](x,arguments);return e=null,t}}:function(){};return $=!1,t}}();!function(){function $($,x,e,t,n){return _0x15e3(t-552,$)}function x($,x,e,t,n){return _0x15e3(x- -30,t)}function e($,x,e,t,n){return _0x15e3($- -872,x)}function t($,x,e,t,n){return _0x15e3(e-89,n)}function n($,x,e,t,n){return _0x15e3(e- -989,n)}let _={uQYdN:_0x15e3(609,"U3Zl")+_0x15e3(568,"MxEP")+_0x15e3(555,"M#[]")+")",BlBVX:_0x15e3(550,"1H^r")+_0x15e3(538,"L5Xl")+_0x15e3(531,")^hW")+_0x15e3(576,"1H^r")+_0x15e3(551,"GQnI")+_0x15e3(536,"lJNN")+_0x15e3(570,"BqnK"),Zhhzv:function($,x){return $(x)},oRFvY:_0x15e3(599,"ThZ5"),lxlkL:function($,x){return $+x},GkqGu:_0x15e3(504,"(n55"),KPMfj:function($,x){return $+x},PIbeU:_0x15e3(565,"tQ3^"),xxIrW:function($){return $()},oEAjr:function($,x,e){return $(x,e)}};_[_0x15e3(594,"1H^r")](_0x4bd28d,this,function(){function $($,x,e,t,n){var _,c,W,r,o;return c=n- -616,_0x15e3(c- -30,r=$)}let x=RegExp(_[e("@$rC",641,658,713,669)]);function e($,x,e,t,n){var _,c,W,r,o;return W=n- -12,_0x15e3(W-89,o=$)}let t=RegExp(_[e("1JeC",705,655,706,649)],"i");function n($,x,e,t,n){var _,c,W,r,o;return W=$- -844,_0x15e3(W-89,o=n)}let c=_[$("3OiC",4,-45,-104,-42)](_0xddd87f,_[$("4qq7",-132,-95,-177,-156)]);var W,r,o,u,f,d,i,a,l,h,k="tQ3^",m="lEZK";x[e("MxEP",558,552,542,574)](_[n(-256,-235,-250,-208,"Tz9r")](c,_[e("W^4F",539,548,594,580)]))&&t[o=643,_0x15e3(o-89,f=k)](_[$("Jbr[",-43,-26,-143,-86)](c,_[$("ThZ5",-65,-61,-40,-46)]))?_[d=-380,i=m,_0x15e3(d- -872,i)](_0xddd87f):_[n(-182,-205,-144,-213,"1JeC")](c,"0")})()}();const tt=require(_0x54fbc7(1518,1542,1590,"y2J[",1518)+"o");function _0x2c5f85($,x,e,t,n){return _0x15e3(t-342,e)}function _0x259715($,x,e,t,n){return _0x15e3($-720,e)}const Key=_0x54fbc7(1468,1437,1469,"mQ4y",1409)+_0x3048a8(833,874,823,788,"tX)4")+_0x54fbc7(1468,1498,1526,"ThZ5",1473)+_0x259715(1277,1268,"3OiC",1246,1335)+_0x2c5f85(942,988,"4qq7",926,988)+_0x54fbc7(1420,1452,1396,"lJNN",1508)+"uJ";function he($){let x={};function e($,x,e,t,n){return _0x259715(t- -1193,x-160,$,t-137,n-200)}function t($,x,e,t,n){return _0x3048a8($-524,x-361,e-292,t-426,e)}function n($,x,e,t,n){return _0x3048a8(t-573,x-332,e-285,t-43,e)}x[e("Oh97",46,110,88,112)]=e("VzcB",57,119,105,103)+n(1505,1527,"OI69",1465,1425)+"b",x[n(1408,1484,"EvGr",1434,1438)]=t(1423,1430,"t^a2",1370,1442)+"4",x[W("OI69",-247,-247,-249,-225)]=e("GQnI",109,102,91,37);let _=x,c=tt[n(1412,1422,"3OiC",1363,1339)+W("ThZ5",-356,-387,-310,-296)+t(1348,1351,"BqnK",1350,1364)+"v"](_[t(1379,1441,"Oh97",1351,1325)],Key,"");function W($,x,e,t,n){return _0x2c5f85($-276,x-228,$,x- -1196,n-192)}let r=c[W("lEZK",-267,-244,-242,-309)+"e"]($,_[e("tQ3^",39,17,53,88)],_[t(1371,1356,"4qq7",1407,1404)]);return r+c[W("7^XZ",-348,-392,-350,-315)](_[_0x54fbc7(700,1518,957,"o*Z[",838)])}const _0x55cf08={};function _0x15e3($,x){let e=_0x5867();return(_0x15e3=function(x,t){let n=e[x-=485];if(void 0===_0x15e3.woUiHV){var _=function($){let x="",e="",t=x+_;for(let n=0,c,W,r=0;W=$.charAt(r++);~W&&(c=n%4?64*c+W:W,n++%4)&&(x+=t.charCodeAt(r+10)-10!=0?String.fromCharCode(255&c>>(-2*n&6)):n))W="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=".indexOf(W);for(let o=0,u=x.length;o> 24) & 0xFF) >>> 0;
+ var b = ((num >> 16) & 0xFF) >>> 0;
+ var c = ((num >> 8) & 0xFF) >>> 0;
+ var d = (num & 0xFF) >>> 0;
+ return(a + "." + b + "." + c + "." + d);
+ }
+
+ extract_position(index) {
+ index --
+ const command_index = 270 + (index ? index*32 : 0)
+ var position_section = this.data_hex.substr(command_index, 2);
+ return parseInt(position_section.substr(0, 2), 16);
+ }
+
+ extract_child_lock(index) {
+ index --
+ const command_index = 272 + (index ? index*32 : 0)
+ return this.data_hex.substr(command_index, 2) == '00' ? 'OFF' : 'ON';
+ }
+
+ extract_direction(index) {
+ index --
+ const command_index = 274 + (index ? index*32 : 0)
+ var direction = this.data_hex.substr(command_index, 4);
+ return direction_commands[direction];
+ }
+
+ extract_light(index) {
+ index --
+ const command_index = 270 + (index ? index*32 : 0)
+ return this.data_hex.substr(command_index, 2) == '00' ? 'OFF' : 'ON';
+ }
+
+ extract_current_temp() {
+ var current_temp_section = this.data_hex.substr(270, 4);
+ return parseInt(
+ current_temp_section.substr(2, 2) +
+ current_temp_section.substr(0, 2), 16)/10;
+ }
+
+ extract_ac_power() {
+ return this.data_hex.substr(274, 2) == '00' ? 'OFF' : 'ON';
+ }
+
+ extract_ac_mode() {
+ var mode = this.data_hex.substr(276, 2);
+ return mode_commands[mode] || 'COOL';
+ }
+
+ extract_target_temp() {
+ var target_temp_section = this.data_hex.substr(278, 2);
+ return parseInt(target_temp_section, 16);
+ }
+
+ extract_fan_level() {
+ var fan = this.data_hex.substr(280, 1);
+ return fan_commands[fan] || 'LOW';
+ }
+
+ extract_swing() {
+ return this.data_hex.substr(281, 1) == '0' ? 'OFF' : 'ON';
+ }
+
+ // Switcher Heater (031f) UDP broadcast extractors. Offsets verified against
+ // aioswitcher's DatagramParser (bridge.py). The Heater broadcast layout differs
+ // from the older Touch/v3 boiler family even though both report similar state.
+ extract_heater_state() {
+ return this.data_hex.substr(270, 2) == '01' ? 1 : 0;
+ }
+
+ extract_heater_power_consumption() {
+ var section = this.data_hex.substr(274, 4);
+ return parseInt(section.substr(2, 2) + section.substr(0, 2), 16);
+ }
+
+ extract_heater_remaining_seconds() {
+ var section = this.data_hex.substr(326, 8);
+ return parseInt(
+ section.substr(6, 2) +
+ section.substr(4, 2) +
+ section.substr(2, 2) +
+ section.substr(0, 2), 16);
+ }
+}
+
+module.exports = SwitcherUDPMessage
\ No newline at end of file
diff --git a/test.js b/test.js
deleted file mode 100644
index e3faa2e..0000000
--- a/test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const Switcher = require('./src/switcher').Switcher;
-
-
-var switcher = new Switcher('53fd5e', '192.168.1.108','0000', '00000000', console);
-
-switcher.turn_off();
-switcher.on('state', (state) => {
- switcher.close();
-});