flaks-node-hon 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/bin/node-hon.js +94 -0
  4. package/cli/ac_apply_preset.js +36 -0
  5. package/cli/ac_generate_preset.js +80 -0
  6. package/cli/ac_turn_off.js +20 -0
  7. package/cli/ac_turn_on.js +21 -0
  8. package/cli/config.js +84 -0
  9. package/cli/purge_cache.js +16 -0
  10. package/cli/show_my_ac_capabilities.js +36 -0
  11. package/cli/show_my_ac_devices.js +19 -0
  12. package/config_example.js +10 -0
  13. package/package.json +41 -0
  14. package/presets/preset_auto.json +7 -0
  15. package/presets/preset_cool.json +8 -0
  16. package/presets/preset_dry.json +6 -0
  17. package/presets/preset_fan.json +8 -0
  18. package/src/ac.js +330 -0
  19. package/src/api.js +123 -0
  20. package/src/appliance-identity.js +71 -0
  21. package/src/appliance.js +282 -0
  22. package/src/auth.js +424 -0
  23. package/src/caching/appliance-cache.js +71 -0
  24. package/src/caching/session-store.js +47 -0
  25. package/src/client.js +253 -0
  26. package/src/command.js +314 -0
  27. package/src/connection.js +73 -0
  28. package/src/constants.js +17 -0
  29. package/src/device.js +29 -0
  30. package/src/errors.js +38 -0
  31. package/src/index.js +25 -0
  32. package/src/lib/config.js +22 -0
  33. package/src/lib/cookie-jar.js +36 -0
  34. package/src/lib/logger.js +56 -0
  35. package/src/lib-cli/_format.js +33 -0
  36. package/src/lib-cli/_get-ac-client.js +29 -0
  37. package/src/lib-cli/_get-client.js +25 -0
  38. package/src/lib-cli/_prompt.js +61 -0
  39. package/src/lib-cli/_run.js +18 -0
  40. package/src/lib-cli/_select-ac.js +36 -0
  41. package/src/parameters.js +261 -0
  42. package/src/preset-generator.js +171 -0
  43. package/types/global.ts +19 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flaks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # hOn Node.js AC Controls
2
+
3
+ A CommonJS Node.js module for hOn authentication and air conditioner controls.
4
+
5
+ ## Advantages
6
+
7
+ - Speed: cached AC preset runs take about 1 second.
8
+ - CLI: easy to set up and use.
9
+ - Module: easy to reuse in other projects.
10
+
11
+ ## About This Project
12
+
13
+ This project is a **Node.js port** of [Andre0512/pyhOn](https://github.com/Andre0512/pyhOn), which was originally developed by Andre0512 under the MIT License.
14
+
15
+ All credit for the original implementation goes to Andre0512. This version reimplements the library in Node.js and may differ in API and functionality to better suit the JavaScript ecosystem.
16
+
17
+ ## Install From NPM
18
+
19
+ After this package is published, install it globally:
20
+
21
+ ```bash
22
+ npm install -g node-hon
23
+ ```
24
+
25
+ Then create a working config in the installed package folder or run from a checkout that has `config.js`, presets, and cache files available.
26
+
27
+ ## Global CLI Setup From This Repo
28
+
29
+ 1. Clone or download this repository.
30
+
31
+ 2. Install dependencies:
32
+
33
+ ```bash
34
+ npm install
35
+ ```
36
+
37
+ 3. Create your local config:
38
+
39
+ ```bash
40
+ cp config_example.js config.js
41
+ ```
42
+
43
+ 4. Edit `config.js` and set your hOn account values.
44
+
45
+ 5. Install this checkout as a global npm command:
46
+
47
+ ```bash
48
+ npm install -g .
49
+ ```
50
+
51
+ 6. Confirm the CLI is available:
52
+
53
+ ```bash
54
+ node-hon list
55
+ ```
56
+
57
+ ## CLI Usage
58
+
59
+ List available air conditioners:
60
+
61
+ ```bash
62
+ node-hon list
63
+ ```
64
+
65
+ Apply a preset by MAC address:
66
+
67
+ ```bash
68
+ node-hon apply xx-xx-xx-xx-xx-xx preset_fan
69
+ ```
70
+
71
+ Apply a preset by AC name:
72
+
73
+ ```bash
74
+ node-hon apply Bedroom preset_fan
75
+ ```
76
+
77
+ Turn an AC off:
78
+
79
+ ```bash
80
+ node-hon apply bedroom off
81
+ ```
82
+
83
+ Generate a preset interactively from live hOn capabilities:
84
+
85
+ ```bash
86
+ node-hon generate-preset
87
+ ```
88
+
89
+ Purge cached session and appliance command data:
90
+
91
+ ```bash
92
+ node-hon purge-cache
93
+ ```
94
+
95
+ The AC identifier can be a MAC address, unique ID, or nickname. Nickname lookup is case-insensitive, so `Bedroom` and `bedroom` can both match the same AC.
96
+
97
+ Generated and bundled presets live in `presets/`. Preset names are passed without `.json`, for example `preset_fan` loads `presets/preset_fan.json`.
98
+
99
+ Runtime cache files live under `cache/` by default, including session and appliance command cache data. Run `node-hon purge-cache`, or set `forceApplianceCacheRefresh: true` in `config.js`, when the appliance command model changes or a preset cannot find a parameter that exists in the app.
100
+
101
+ ## Development CLI
102
+
103
+ You can also run the CLI scripts directly from the repository:
104
+
105
+ ```bash
106
+ node cli/show_my_ac_devices.js
107
+ node cli/ac_generate_preset.js
108
+ ```
109
+
110
+ `ac_generate_preset.js` loads the latest AC capabilities from the hOn API. You can set `AC_ID` or `PRESET_NAME` to preselect the air conditioner or output preset name.
111
+
112
+ ## Tests
113
+
114
+ ```bash
115
+ npm test
116
+ ```
117
+
118
+ ## Publishing
119
+
120
+ Before publishing, make sure you are logged in:
121
+
122
+ ```bash
123
+ npm login
124
+ npm whoami
125
+ ```
126
+
127
+ Verify the package:
128
+
129
+ ```bash
130
+ npm test
131
+ npm run typecheck
132
+ npm pack --dry-run
133
+ ```
134
+
135
+ Publish:
136
+
137
+ ```bash
138
+ npm publish --access public
139
+ ```
140
+
141
+ ### License
142
+
143
+ This project is released under the MIT License.
144
+ The original Python library is also MIT-licensed. See the `LICENSE` file for details and attribution.
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const path = require("node:path");
5
+
6
+ const packageRoot = path.resolve(__dirname, "..");
7
+
8
+ function usage() {
9
+ return [
10
+ "Usage:",
11
+ " node-hon apply <mac|name> <preset_name|off>",
12
+ " node-hon config",
13
+ " node-hon list",
14
+ " node-hon generate-preset",
15
+ " node-hon purge-cache"
16
+ ].join("\n");
17
+ }
18
+
19
+ async function run(argv = process.argv.slice(2), options = {}) {
20
+ const stderr = options.stderr || process.stderr;
21
+ const commands = options.commands || defaultCommands();
22
+ const baseDir = options.baseDir || packageRoot;
23
+ const [command, ...args] = argv;
24
+
25
+ if (command === "apply") {
26
+ const [acId, presetName, ...rest] = args;
27
+ if (!acId || !presetName || rest.length) {
28
+ stderr.write(`${usage()}\n`);
29
+ return 1;
30
+ }
31
+ await commands.apply({ acId, presetName, baseDir });
32
+ return 0;
33
+ }
34
+
35
+ if (command === "generate-preset") {
36
+ if (args.length) {
37
+ stderr.write(`${usage()}\n`);
38
+ return 1;
39
+ }
40
+ await commands.generatePreset({ baseDir });
41
+ return 0;
42
+ }
43
+
44
+ if (command === "config") {
45
+ if (args.length) {
46
+ stderr.write(`${usage()}\n`);
47
+ return 1;
48
+ }
49
+ await commands.config({ baseDir });
50
+ return 0;
51
+ }
52
+
53
+ if (command === "list") {
54
+ if (args.length) {
55
+ stderr.write(`${usage()}\n`);
56
+ return 1;
57
+ }
58
+ await commands.list({ baseDir });
59
+ return 0;
60
+ }
61
+
62
+ if (command === "purge-cache") {
63
+ if (args.length) {
64
+ stderr.write(`${usage()}\n`);
65
+ return 1;
66
+ }
67
+ await commands.purgeCache({ baseDir });
68
+ return 0;
69
+ }
70
+
71
+ stderr.write(`${usage()}\n`);
72
+ return 1;
73
+ }
74
+
75
+ function defaultCommands() {
76
+ return {
77
+ apply: require("../cli/ac_apply_preset").main,
78
+ config: require("../cli/config").main,
79
+ list: require("../cli/show_my_ac_devices").main,
80
+ generatePreset: require("../cli/ac_generate_preset").main,
81
+ purgeCache: require("../cli/purge_cache").main
82
+ };
83
+ }
84
+
85
+ if (require.main === module) {
86
+ run().then((code) => {
87
+ process.exitCode = code;
88
+ }).catch((error) => {
89
+ console.error(error);
90
+ process.exitCode = 1;
91
+ });
92
+ }
93
+
94
+ module.exports = { run, usage };
@@ -0,0 +1,36 @@
1
+ const path = require("node:path");
2
+ const getAcClient = require("../src/lib-cli/_get-ac-client");
3
+ const { formatAc } = require("../src/lib-cli/_format");
4
+ const { handleCliError } = require("../src/lib-cli/_run");
5
+
6
+ async function main(options = {}) {
7
+ const baseDir = options.baseDir || path.resolve(__dirname, "..");
8
+ const loadAcClient = options.getAcClient || getAcClient;
9
+ const { ac, client } = await loadAcClient({ ...options, baseDir });
10
+
11
+ try {
12
+ const presetName = options.presetName || process.env.PRESET_NAME || "preset_fan";
13
+ if (presetName === "off") {
14
+ await ac.powerOff();
15
+ console.log(`Powered off ${formatAc(ac)}`);
16
+ return;
17
+ }
18
+ const presetFile = path.resolve(
19
+ baseDir,
20
+ "presets",
21
+ `${presetName}.json`,
22
+ );
23
+
24
+ const preset = require(presetFile);
25
+ await ac.applyPreset(preset, presetName);
26
+ console.log(`Applied ${presetName} to ${formatAc(ac)}`);
27
+ } finally {
28
+ await client.close();
29
+ }
30
+ }
31
+
32
+ if (require.main === module) {
33
+ main().catch(handleCliError);
34
+ }
35
+
36
+ module.exports = { main };
@@ -0,0 +1,80 @@
1
+ const fs = require("node:fs/promises");
2
+ const path = require("node:path");
3
+ const getClient = require("../src/lib-cli/_get-client");
4
+ const { createAsk, promptChoice } = require("../src/lib-cli/_prompt");
5
+ const { formatAc, printSkipped } = require("../src/lib-cli/_format");
6
+ const { handleCliError } = require("../src/lib-cli/_run");
7
+ const { selectAirConditioner } = require("../src/lib-cli/_select-ac");
8
+ const {
9
+ buildPreset,
10
+ defaultValueForField,
11
+ getFieldDescriptors,
12
+ getModeOptions,
13
+ selectPresetCommand
14
+ } = require("../src/preset-generator");
15
+
16
+ async function main(options = {}) {
17
+ const baseDir = options.baseDir || path.resolve(__dirname, "..");
18
+ const client = await getClient({ ...options, baseDir });
19
+ const ask = options.ask || createAsk();
20
+ try {
21
+ const airConditioners = await client.getAirConditioners();
22
+ if (!airConditioners.length) {
23
+ console.log("No air conditioners found.");
24
+ return;
25
+ }
26
+ const selectedAc = await selectAirConditioner(ask, airConditioners, options.acId);
27
+ const selectedCommand = selectPresetCommand(selectedAc.capabilities());
28
+ const generatorMode = await promptChoice(ask, "Generator mode", ["basic", "advanced"], "basic");
29
+ const modeOptions = getModeOptions(selectedCommand.command);
30
+ const presetMode = modeOptions.length ? await promptChoice(ask, "Preset mode", modeOptions, modeOptions[0]) : "";
31
+ const { fields, skipped } = getFieldDescriptors(selectedCommand.command, generatorMode);
32
+ const values = {};
33
+ for (const field of fields) {
34
+ values[field.name] = await promptField(ask, field);
35
+ }
36
+ const preset = buildPreset(selectedCommand.command, values, presetMode);
37
+ const presetName = await promptPresetName(ask, options.presetName);
38
+ const outputFile = path.resolve(baseDir, "presets", `${presetName}.json`);
39
+ await fs.mkdir(path.dirname(outputFile), { recursive: true });
40
+ await fs.writeFile(outputFile, `${JSON.stringify(preset, null, 2)}\n`);
41
+ console.log(`Generated ${path.relative(process.cwd(), outputFile)} for ${formatAc(selectedAc)}`);
42
+ console.log(`Selected command: ${selectedCommand.name}${presetMode ? ` (${presetMode})` : ""}`);
43
+ printSkipped(skipped);
44
+ } finally {
45
+ if (ask.close) {
46
+ ask.close();
47
+ }
48
+ await client.close();
49
+ }
50
+ }
51
+
52
+ async function promptPresetName(ask, presetName = "") {
53
+ const fallback = presetName || process.env.PRESET_NAME || "preset_custom";
54
+ for (;;) {
55
+ const answer = (await ask.question(`Preset filename [${fallback}]: `)).trim() || fallback;
56
+ const safe = answer.replace(/\.json$/i, "");
57
+ if (/^[a-zA-Z0-9_.-]+$/.test(safe)) {
58
+ return safe;
59
+ }
60
+ console.log("Use only letters, numbers, dot, underscore, and dash.");
61
+ }
62
+ }
63
+
64
+ async function promptField(ask, field) {
65
+ const fallback = defaultValueForField(field);
66
+ for (;;) {
67
+ const suffix = field.values.length ? ` (${field.values.join(", ")})` : "";
68
+ const answer = (await ask.question(`${field.name}${suffix} [${fallback}]: `)).trim() || fallback;
69
+ if (field.values.includes(String(answer))) {
70
+ return String(answer);
71
+ }
72
+ console.log(`Allowed values: ${field.values.join(", ")}`);
73
+ }
74
+ }
75
+
76
+ if (require.main === module) {
77
+ main().catch(handleCliError);
78
+ }
79
+
80
+ module.exports = { main };
@@ -0,0 +1,20 @@
1
+ const getAcClient = require("../src/lib-cli/_get-ac-client");
2
+ const { formatAc } = require("../src/lib-cli/_format");
3
+ const { handleCliError } = require("../src/lib-cli/_run");
4
+
5
+ async function main(options = {}) {
6
+ const { ac, client } = await getAcClient(options);
7
+
8
+ try {
9
+ await ac.powerOff();
10
+ console.log(`Turned off ${formatAc(ac)}`);
11
+ } finally {
12
+ await client.close();
13
+ }
14
+ }
15
+
16
+ if (require.main === module) {
17
+ main().catch(handleCliError);
18
+ }
19
+
20
+ module.exports = { main };
@@ -0,0 +1,21 @@
1
+ const getAcClient = require("../src/lib-cli/_get-ac-client");
2
+ const { formatAc } = require("../src/lib-cli/_format");
3
+ const { handleCliError } = require("../src/lib-cli/_run");
4
+
5
+ async function main(options = {}) {
6
+ const { ac, client } = await getAcClient(options);
7
+
8
+ try {
9
+ await ac.powerOn();
10
+
11
+ console.log(`Turned on ${formatAc(ac)}`);
12
+ } finally {
13
+ await client.close();
14
+ }
15
+ }
16
+
17
+ if (require.main === module) {
18
+ main().catch(handleCliError);
19
+ }
20
+
21
+ module.exports = { main };
package/cli/config.js ADDED
@@ -0,0 +1,84 @@
1
+ const fs = require("node:fs/promises");
2
+ const path = require("node:path");
3
+ const { stdout: output } = require("node:process");
4
+ const { askBoolean, askText, createAsk } = require("../src/lib-cli/_prompt");
5
+ const { handleCliError } = require("../src/lib-cli/_run");
6
+
7
+ async function main(options = {}) {
8
+ const baseDir = options.baseDir || path.resolve(__dirname, "..");
9
+ const configPath = options.configPath || path.resolve(baseDir, "config.js");
10
+ const examplePath = path.resolve(baseDir, "config_example.js");
11
+ const current = await loadConfigOrDefaults(configPath, examplePath);
12
+ const ask = options.ask || createAsk();
13
+
14
+ try {
15
+ const config = {
16
+ email: await askText(ask, "hOn email", current.email),
17
+ password: await askText(ask, "hOn password", "", current.password),
18
+ mobileId: await askText(ask, "Mobile ID", current.mobileId || "pyhOn-node"),
19
+ sessionFile: await askText(ask, "Session file", current.sessionFile || "./cache/.hon-session.json"),
20
+ applianceCacheFile: await askText(ask, "Appliance cache file", current.applianceCacheFile || "./cache/.hon-appliance-cache.json"),
21
+ forceApplianceCacheRefresh: await askBoolean(ask, "Force appliance cache refresh", Boolean(current.forceApplianceCacheRefresh)),
22
+ debug: await askBoolean(ask, "Debug logging", Boolean(current.debug))
23
+ };
24
+ await fs.writeFile(configPath, buildConfigText(config), "utf8");
25
+ output.write(`Saved ${configPath}\n`);
26
+ } finally {
27
+ if (ask.close) {
28
+ ask.close();
29
+ }
30
+ }
31
+ }
32
+
33
+ async function loadConfigOrDefaults(configPath, examplePath) {
34
+ if (await exists(configPath)) {
35
+ return requireFresh(configPath);
36
+ }
37
+ if (await exists(examplePath)) {
38
+ return requireFresh(examplePath);
39
+ }
40
+ return {
41
+ email: "user@example.com",
42
+ password: "password",
43
+ mobileId: "pyhOn-node",
44
+ sessionFile: "./cache/.hon-session.json",
45
+ applianceCacheFile: "./cache/.hon-appliance-cache.json",
46
+ forceApplianceCacheRefresh: false,
47
+ debug: false
48
+ };
49
+ }
50
+
51
+ function requireFresh(filePath) {
52
+ const resolved = require.resolve(path.resolve(filePath));
53
+ delete require.cache[resolved];
54
+ return require(resolved);
55
+ }
56
+
57
+ async function exists(filePath) {
58
+ try {
59
+ await fs.access(filePath);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ function buildConfigText(config) {
67
+ return `/** @type {import("./types/global").ProjectConfig} */
68
+ module.exports = {
69
+ email: ${JSON.stringify(config.email)},
70
+ password: ${JSON.stringify(config.password)},
71
+ mobileId: ${JSON.stringify(config.mobileId)},
72
+ sessionFile: ${JSON.stringify(config.sessionFile)},
73
+ applianceCacheFile: ${JSON.stringify(config.applianceCacheFile)},
74
+ forceApplianceCacheRefresh: ${Boolean(config.forceApplianceCacheRefresh)},
75
+ debug: ${Boolean(config.debug)}
76
+ };
77
+ `;
78
+ }
79
+
80
+ if (require.main === module) {
81
+ main().catch(handleCliError);
82
+ }
83
+
84
+ module.exports = { buildConfigText, main };
@@ -0,0 +1,16 @@
1
+ const fs = require("node:fs/promises");
2
+ const path = require("node:path");
3
+ const { handleCliError } = require("../src/lib-cli/_run");
4
+
5
+ async function main(options = {}) {
6
+ const baseDir = options.baseDir || path.resolve(__dirname, "..");
7
+ const cacheDir = options.cacheDir || path.resolve(baseDir, "cache");
8
+ await fs.rm(cacheDir, { recursive: true, force: true });
9
+ console.log(`Purged cache: ${cacheDir}`);
10
+ }
11
+
12
+ if (require.main === module) {
13
+ main().catch(handleCliError);
14
+ }
15
+
16
+ module.exports = { main };
@@ -0,0 +1,36 @@
1
+ const fs = require("node:fs");
2
+ const getClient = require("../src/lib-cli/_get-client");
3
+
4
+ async function main(options = {}) {
5
+ const client = await getClient(options);
6
+
7
+ try {
8
+ const airConditioners = await client.getAirConditioners();
9
+ if (!airConditioners.length) {
10
+ console.log("No air conditioners found.");
11
+ return;
12
+ }
13
+
14
+ const filename = "./hon-devices-capabilities.json";
15
+ const mapping = {};
16
+ for (const ac of airConditioners) {
17
+ const naming = `${ac.nickName}_${ac.macAddress}`;
18
+ console.log(naming);
19
+
20
+ mapping[naming] = ac.capabilities();
21
+ }
22
+
23
+ fs.writeFileSync(filename, JSON.stringify(mapping, null, 2));
24
+ } finally {
25
+ await client.close();
26
+ }
27
+ }
28
+
29
+ if (require.main === module) {
30
+ main().catch((error) => {
31
+ console.error(error);
32
+ process.exitCode = 1;
33
+ });
34
+ }
35
+
36
+ module.exports = { main };
@@ -0,0 +1,19 @@
1
+ const getClient = require("../src/lib-cli/_get-client");
2
+ const { printAcList } = require("../src/lib-cli/_format");
3
+ const { handleCliError } = require("../src/lib-cli/_run");
4
+
5
+ async function main(options = {}) {
6
+ const client = await getClient(options);
7
+ try {
8
+ const airConditioners = await client.getAirConditioners();
9
+ printAcList(airConditioners);
10
+ } finally {
11
+ await client.close();
12
+ }
13
+ }
14
+
15
+ if (require.main === module) {
16
+ main().catch(handleCliError);
17
+ }
18
+
19
+ module.exports = { main };
@@ -0,0 +1,10 @@
1
+ /** @type {import("./types/global").ProjectConfig} */
2
+ module.exports = {
3
+ email: "user@example.com",
4
+ password: "password",
5
+ mobileId: "pyhOn-node",
6
+ sessionFile: "./cache/.hon-session.json",
7
+ applianceCacheFile: "./cache/.hon-appliance-cache.json",
8
+ forceApplianceCacheRefresh: false,
9
+ debug: false
10
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "flaks-node-hon",
3
+ "version": "1.0.0",
4
+ "description": "CommonJS Node.js client for hOn auth and air conditioner controls",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "node-hon": "bin/node-hon.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "cli/",
12
+ "src/",
13
+ "presets/",
14
+ "types/",
15
+ "config_example.js",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "type": "commonjs",
20
+ "author": "Flaks",
21
+ "engines": {
22
+ "node": ">=23"
23
+ },
24
+ "scripts": {
25
+ "test": "node --test \"test/*.test.js\"",
26
+ "typecheck": "tsc --noEmit --project jsconfig.json",
27
+ "prepublishOnly": "npm test && npm run typecheck"
28
+ },
29
+ "keywords": [
30
+ "hon",
31
+ "haier",
32
+ "candy",
33
+ "hoover",
34
+ "air-conditioner"
35
+ ],
36
+ "license": "MIT",
37
+ "devDependencies": {
38
+ "@types/node": "^25.9.1",
39
+ "typescript": "^6.0.3"
40
+ }
41
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "mode": "iot_uv_and_auto",
3
+ "tempSel": "24",
4
+ "windSpeed": "3",
5
+ "windDirectionVertical": "2",
6
+ "windDirectionHorizontal": "6"
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mode": "iot_uv_and_cool",
3
+ "tempSel": "24",
4
+ "windSpeed": "2",
5
+ "windDirectionVertical": "2",
6
+ "windDirectionHorizontal": "0",
7
+ "healthMode": "1"
8
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "mode": "iot_uv_and_dry",
3
+ "tempSel": "24",
4
+ "windSpeed": "2",
5
+ "windDirectionVertical": "5"
6
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mode": "iot_uv_and_fan",
3
+ "tempSel": "24",
4
+ "windSpeed": "2",
5
+ "windDirectionVertical": "2",
6
+ "windDirectionHorizontal": "0",
7
+ "healthMode": "1"
8
+ }