sesame-kit 0.4.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.
- package/LICENSE +26 -0
- package/LICENSE.biz3 +21 -0
- package/README.ja.md +225 -0
- package/README.md +222 -0
- package/bin/sesame.js +8 -0
- package/clients/js/sesame-client.mjs +208 -0
- package/clients/python/pyproject.toml +5 -0
- package/clients/python/sesame_client.py +323 -0
- package/clients/python/setup.cfg +11 -0
- package/docs/architecture.ja.md +132 -0
- package/docs/architecture.md +105 -0
- package/docs/commands.ja.md +316 -0
- package/docs/commands.md +308 -0
- package/docs/library.ja.md +152 -0
- package/docs/library.md +152 -0
- package/docs/migration.ja.md +13 -0
- package/docs/migration.md +13 -0
- package/package.json +114 -0
- package/src/access.js +375 -0
- package/src/account.js +36 -0
- package/src/auth.js +248 -0
- package/src/ble/devicemodel.js +164 -0
- package/src/ble/index.js +185 -0
- package/src/ble/protocol.js +319 -0
- package/src/ble/session.js +235 -0
- package/src/ble/transport.js +279 -0
- package/src/cli/access.js +373 -0
- package/src/cli/company.js +104 -0
- package/src/cli/iot.js +400 -0
- package/src/cli/org.js +788 -0
- package/src/cli/presetir.js +188 -0
- package/src/cli/schedule.js +83 -0
- package/src/cli/serve.js +308 -0
- package/src/cli.js +1815 -0
- package/src/client.js +957 -0
- package/src/company.js +147 -0
- package/src/config.js +575 -0
- package/src/crypto.js +162 -0
- package/src/devices.js +228 -0
- package/src/index.js +55 -0
- package/src/iot.js +513 -0
- package/src/ir.js +341 -0
- package/src/itemcodes.js +29 -0
- package/src/lock.js +194 -0
- package/src/org.js +803 -0
- package/src/paths.js +30 -0
- package/src/presetir.js +525 -0
- package/src/prompts.js +74 -0
- package/src/schedule.js +108 -0
- package/src/serve/daemon.js +251 -0
- package/src/serve/framing/grpc.js +145 -0
- package/src/serve/framing/http.js +144 -0
- package/src/serve/framing/ndjson.js +75 -0
- package/src/serve/framing/socket.js +73 -0
- package/src/serve/framing/stdio.js +28 -0
- package/src/serve/framing/token.js +36 -0
- package/src/serve/framing/ws.js +56 -0
- package/src/serve/grpc-methods.generated.json +378 -0
- package/src/serve/jsonrpc.js +164 -0
- package/src/serve/registry.js +226 -0
- package/src/serve/rpc-params.generated.json +1746 -0
- package/src/serve/sesame.proto +470 -0
- package/src/session-ui.js +181 -0
- package/src/sharekey.js +130 -0
- package/src/tokens.js +53 -0
- package/src/transport.js +634 -0
- package/src/util.js +26 -0
- package/types/access.d.ts +193 -0
- package/types/access.d.ts.map +1 -0
- package/types/account.d.ts +13 -0
- package/types/account.d.ts.map +1 -0
- package/types/auth.d.ts +80 -0
- package/types/auth.d.ts.map +1 -0
- package/types/ble/devicemodel.d.ts +212 -0
- package/types/ble/devicemodel.d.ts.map +1 -0
- package/types/ble/index.d.ts +160 -0
- package/types/ble/index.d.ts.map +1 -0
- package/types/ble/protocol.d.ts +201 -0
- package/types/ble/protocol.d.ts.map +1 -0
- package/types/ble/session.d.ts +129 -0
- package/types/ble/session.d.ts.map +1 -0
- package/types/ble/transport.d.ts +67 -0
- package/types/ble/transport.d.ts.map +1 -0
- package/types/cli/access.d.ts +6 -0
- package/types/cli/access.d.ts.map +1 -0
- package/types/cli/company.d.ts +6 -0
- package/types/cli/company.d.ts.map +1 -0
- package/types/cli/iot.d.ts +6 -0
- package/types/cli/iot.d.ts.map +1 -0
- package/types/cli/org.d.ts +6 -0
- package/types/cli/org.d.ts.map +1 -0
- package/types/cli/presetir.d.ts +6 -0
- package/types/cli/presetir.d.ts.map +1 -0
- package/types/cli/schedule.d.ts +6 -0
- package/types/cli/schedule.d.ts.map +1 -0
- package/types/cli/serve.d.ts +2 -0
- package/types/cli/serve.d.ts.map +1 -0
- package/types/cli.d.ts +2 -0
- package/types/cli.d.ts.map +1 -0
- package/types/client.d.ts +463 -0
- package/types/client.d.ts.map +1 -0
- package/types/company.d.ts +94 -0
- package/types/company.d.ts.map +1 -0
- package/types/config.d.ts +111 -0
- package/types/config.d.ts.map +1 -0
- package/types/crypto.d.ts +61 -0
- package/types/crypto.d.ts.map +1 -0
- package/types/devices.d.ts +116 -0
- package/types/devices.d.ts.map +1 -0
- package/types/index.d.ts +23 -0
- package/types/index.d.ts.map +1 -0
- package/types/iot.d.ts +312 -0
- package/types/iot.d.ts.map +1 -0
- package/types/ir.d.ts +147 -0
- package/types/ir.d.ts.map +1 -0
- package/types/itemcodes.d.ts +21 -0
- package/types/itemcodes.d.ts.map +1 -0
- package/types/lock.d.ts +89 -0
- package/types/lock.d.ts.map +1 -0
- package/types/org.d.ts +468 -0
- package/types/org.d.ts.map +1 -0
- package/types/paths.d.ts +10 -0
- package/types/paths.d.ts.map +1 -0
- package/types/presetir.d.ts +286 -0
- package/types/presetir.d.ts.map +1 -0
- package/types/prompts.d.ts +39 -0
- package/types/prompts.d.ts.map +1 -0
- package/types/schedule.d.ts +71 -0
- package/types/schedule.d.ts.map +1 -0
- package/types/serve/daemon.d.ts +133 -0
- package/types/serve/daemon.d.ts.map +1 -0
- package/types/serve/framing/grpc.d.ts +14 -0
- package/types/serve/framing/grpc.d.ts.map +1 -0
- package/types/serve/framing/http.d.ts +14 -0
- package/types/serve/framing/http.d.ts.map +1 -0
- package/types/serve/framing/ndjson.d.ts +19 -0
- package/types/serve/framing/ndjson.d.ts.map +1 -0
- package/types/serve/framing/socket.d.ts +14 -0
- package/types/serve/framing/socket.d.ts.map +1 -0
- package/types/serve/framing/stdio.d.ts +11 -0
- package/types/serve/framing/stdio.d.ts.map +1 -0
- package/types/serve/framing/token.d.ts +11 -0
- package/types/serve/framing/token.d.ts.map +1 -0
- package/types/serve/framing/ws.d.ts +13 -0
- package/types/serve/framing/ws.d.ts.map +1 -0
- package/types/serve/jsonrpc.d.ts +118 -0
- package/types/serve/jsonrpc.d.ts.map +1 -0
- package/types/serve/registry.d.ts +41 -0
- package/types/serve/registry.d.ts.map +1 -0
- package/types/session-ui.d.ts +36 -0
- package/types/session-ui.d.ts.map +1 -0
- package/types/sharekey.d.ts +35 -0
- package/types/sharekey.d.ts.map +1 -0
- package/types/tokens.d.ts +20 -0
- package/types/tokens.d.ts.map +1 -0
- package/types/transport.d.ts +138 -0
- package/types/transport.d.ts.map +1 -0
- package/types/util.d.ts +20 -0
- package/types/util.d.ts.map +1 -0
- package/vendor/biz3/README.md +37 -0
- package/vendor/biz3/constants/cmdCode.d.ts +48 -0
- package/vendor/biz3/constants/cmdCode.d.ts.map +1 -0
- package/vendor/biz3/constants/cmdCode.js +92 -0
- package/vendor/biz3/constants/messageConstants.d.ts +28 -0
- package/vendor/biz3/constants/messageConstants.d.ts.map +1 -0
- package/vendor/biz3/constants/messageConstants.js +30 -0
- package/vendor/biz3/constants/sesameDeviceModel.d.ts +75 -0
- package/vendor/biz3/constants/sesameDeviceModel.d.ts.map +1 -0
- package/vendor/biz3/constants/sesameDeviceModel.js +77 -0
- package/vendor/biz3/package.json +5 -0
package/src/paths.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// 設定ディレクトリ解決。優先順位:
|
|
2
|
+
// 1. 明示渡し (overrideDir, CLI --config-dir)
|
|
3
|
+
// 2. env SESAME_HUB3_HOME (アプリ専用)
|
|
4
|
+
// 3. env XDG_CONFIG_HOME → $XDG_CONFIG_HOME/sesame-hub3
|
|
5
|
+
// 4. ~/.config/sesame-hub3
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
|
|
9
|
+
const APP_DIRNAME = "sesame-hub3";
|
|
10
|
+
|
|
11
|
+
export function resolveConfigDir(overrideDir) {
|
|
12
|
+
if (overrideDir) return resolve(overrideDir);
|
|
13
|
+
if (process.env.SESAME_HUB3_HOME) return resolve(process.env.SESAME_HUB3_HOME);
|
|
14
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
15
|
+
if (xdg) return resolve(xdg, APP_DIRNAME);
|
|
16
|
+
return resolve(homedir(), ".config", APP_DIRNAME);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function configPaths(overrideDir) {
|
|
20
|
+
const dir = resolveConfigDir(overrideDir);
|
|
21
|
+
return {
|
|
22
|
+
dir,
|
|
23
|
+
config: resolve(dir, "config.json"),
|
|
24
|
+
tokens: resolve(dir, "tokens.json"),
|
|
25
|
+
loginState: resolve(dir, "login_state.json"),
|
|
26
|
+
devices: resolve(dir, "devices.json"),
|
|
27
|
+
// `sesame serve` の Unix domain socket (POSIX 専用。dir 自体が 0700)。
|
|
28
|
+
socket: resolve(dir, "sesame.sock"),
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/presetir.js
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
// プリセット (HXD) IR リモコンの command 生成 + 発射 (remoteEmit)。
|
|
2
|
+
//
|
|
3
|
+
// Ported from biz3 (CANDY-HOUSE/biz3, MIT):
|
|
4
|
+
// - vendor reference: references_web/src/pages/personal/devices/wifi-module/ir/utils/HXDCommandProcessor.js
|
|
5
|
+
// - vendor reference: references_web/src/pages/personal/devices/wifi-module/ir/utils/HXDParametersSwapper.js
|
|
6
|
+
// - vendor reference: references_web/src/pages/personal/devices/wifi-module/ir/remote-air/index.js
|
|
7
|
+
// - vendor reference: references_web/src/pages/personal/devices/wifi-module/ir/remote-non-air/index.js
|
|
8
|
+
// - vendor reference: references_web/src/api/useRemoteCtrl.js:460-484 (sendIR frame), 65-80 (response)
|
|
9
|
+
//
|
|
10
|
+
// 概要:
|
|
11
|
+
// メーカー DB から取得したプリセットリモコン (remote.code を持つ) に対し、
|
|
12
|
+
// エアコン/TV/照明/扇風機の HEX command 文字列をローカル生成し、
|
|
13
|
+
// 既存 sendIR (op:'sendIR', operation:'remoteEmit') で Hub3 に投げる。
|
|
14
|
+
// presetIR 固有の新 WS op は無い (sendIR は learnIR 等と完全共通)。
|
|
15
|
+
//
|
|
16
|
+
// ⚠️ irType (= remote.type) は **実値** をそのまま渡す:
|
|
17
|
+
// 0xC000=エアコン, 0x8000=扇風機, 0xE000=ライト, 0x2000=TV。
|
|
18
|
+
// (UI メニュー id の 0xFEFF=学習 はここでは扱わない。crypto.js / ir.js の IR_TYPE トラップ参照)
|
|
19
|
+
//
|
|
20
|
+
// ⚠️ getAirKey の keyMap トラップ (過去バグ源・biz3 をそのまま再現):
|
|
21
|
+
// remote-air/index.js:122 は buildCommand 内で getAirKey(item.type) を呼ぶが、
|
|
22
|
+
// airControlItems (remote-air:238-302) の item.type は
|
|
23
|
+
// 'POWER_ON','POWER_OFF','TEMP_ADD','TEMP_REDUCE','MODE','FAN_SPEED','WIND_DIRECTION','AUTO_SWING'。
|
|
24
|
+
// 一方 getAirKey の keyMap キーは 'POWER_STATUS_ON','POWER_STATUS_OFF',
|
|
25
|
+
// 'TEMP_CONTROL_ADD','TEMP_CONTROL_REDUCE','MODE','FAN_SPEED','WIND_DIRECTION','AUTO_WIND_DIRECTION'。
|
|
26
|
+
// → POWER_*/TEMP_*/AUTO_SWING は keyMap に無く default 0x01 にフォールバックする。
|
|
27
|
+
// MODE/FAN_SPEED/WIND_DIRECTION のみ一致。
|
|
28
|
+
// Air では key は buf[9] に入るだけで、実際の状態 (温度/モード/風速/風向/電源) は
|
|
29
|
+
// buildAirCommand が buf[4..10] へ直接書き込むため、key の値は発射動作にほぼ影響しない。
|
|
30
|
+
// 本実装は biz3 の keyMap をそのまま移植し、UI type → keyMap キーの読み替えは行わない
|
|
31
|
+
// (biz3 の挙動を 1bit も変えない)。呼び出し側で keyMap のキー名を直接渡すこともできる。
|
|
32
|
+
|
|
33
|
+
import { ACTION_TYPES } from "../vendor/biz3/constants/messageConstants.js";
|
|
34
|
+
import { assertSuccess } from "./util.js";
|
|
35
|
+
|
|
36
|
+
const ACTION = ACTION_TYPES.BIZ3_IR_REMOTE; // "biz3IRRemote" (vendor 由来)
|
|
37
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* irType (remote.type) の確定値。
|
|
41
|
+
* 一次資料: presetIR.json extraLogic C / HXDParametersSwapper.getKeyByDeviceType:166-180。
|
|
42
|
+
* @readonly
|
|
43
|
+
*/
|
|
44
|
+
export const IR_TYPE = Object.freeze({
|
|
45
|
+
AIR: 0xc000, // エアコン (remote-air ページ)
|
|
46
|
+
FAN: 0x8000, // 扇風機
|
|
47
|
+
LIGHT: 0xe000, // ライト
|
|
48
|
+
TV: 0x2000, // TV
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// =====================================================================
|
|
52
|
+
// A. HXDCommandProcessor (vendor: HXDCommandProcessor.js)
|
|
53
|
+
// 内部状態 setter チェーン → buildAir/NonAirCommand で 16 byte を生成。
|
|
54
|
+
// =====================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* HXD プリセット command の組み立て器。
|
|
58
|
+
* biz3 HXDCommandProcessor.js を 1:1 移植 (constructor 既定値・byte 配置・checksum 完全一致)。
|
|
59
|
+
*/
|
|
60
|
+
export class HXDCommandProcessor {
|
|
61
|
+
constructor() {
|
|
62
|
+
// 既定値 (HXDCommandProcessor.js:3-15)
|
|
63
|
+
this.power = 0x00;
|
|
64
|
+
this.temperature = 25;
|
|
65
|
+
this.fanSpeed = 0x01;
|
|
66
|
+
this.windDirection = 0x02;
|
|
67
|
+
this.autoWindDirection = 0x01;
|
|
68
|
+
this.mode = 0x02;
|
|
69
|
+
this.key = 0x01;
|
|
70
|
+
this.code = 0x00;
|
|
71
|
+
this.defaultTable = [0, 0, 0];
|
|
72
|
+
this.AirPrefixCode = [0x30, 0x01];
|
|
73
|
+
this.commonPrefixCode = [0x30, 0x00];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* エアコン command (16 byte) を生成。
|
|
78
|
+
* vendor: HXDCommandProcessor.js:17-34。
|
|
79
|
+
* 配置: [0x30,0x01, codeHi,codeLo, temp,fanSpeed,windDir,autoWind,power,key,mode, 1,0,0, 0xff, checksum]
|
|
80
|
+
* @returns {number[]} 16 byte の配列
|
|
81
|
+
*/
|
|
82
|
+
buildAirCommand() {
|
|
83
|
+
const buf = this.buildKeyData(this.AirPrefixCode, this.code, this.defaultTable);
|
|
84
|
+
buf[4] = this.temperature;
|
|
85
|
+
buf[5] = this.fanSpeed;
|
|
86
|
+
buf[6] = this.windDirection;
|
|
87
|
+
buf[7] = this.autoWindDirection;
|
|
88
|
+
buf[8] = this.power;
|
|
89
|
+
buf[9] = this.key;
|
|
90
|
+
buf[10] = this.mode;
|
|
91
|
+
buf[buf.length - 2] = 0xff;
|
|
92
|
+
|
|
93
|
+
// checksum: 末尾 1 byte を除く先頭全 byte の総和の下位 8bit (HXDCommandProcessor.js:29-30)
|
|
94
|
+
const checkSum = buf.slice(0, -1).reduce((sum, byte) => sum + byte, 0);
|
|
95
|
+
buf[buf.length - 1] = checkSum & 0xff;
|
|
96
|
+
return buf;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 非エアコン (TV/ライト/扇風機) command (16 byte) を生成。
|
|
101
|
+
* vendor: HXDCommandProcessor.js:36-47。
|
|
102
|
+
* 配置: [0x30,0x00, codeHi,codeLo, 0,0,0,0,0, key, 0, 1,0,0, 0xff, checksum]
|
|
103
|
+
* @returns {number[]} 16 byte の配列
|
|
104
|
+
*/
|
|
105
|
+
buildNonAirCommand() {
|
|
106
|
+
const buf = this.buildKeyData(this.commonPrefixCode, this.code, this.defaultTable);
|
|
107
|
+
buf[9] = this.key;
|
|
108
|
+
buf[buf.length - 2] = 0xff;
|
|
109
|
+
|
|
110
|
+
const checkSum = buf.slice(0, -1).reduce((sum, byte) => sum + byte, 0);
|
|
111
|
+
buf[buf.length - 1] = checkSum & 0xff;
|
|
112
|
+
return buf;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* command の骨格 (16 byte) を生成。Air/NonAir 共通。
|
|
117
|
+
* vendor: HXDCommandProcessor.js:49-71。
|
|
118
|
+
* @param {number[]} prefixCodeArray 2 byte prefix ([0x30,0x01] か [0x30,0x00])
|
|
119
|
+
* @param {number} code remote.code (16bit, ビッグエンディアンで 2 byte に分割)
|
|
120
|
+
* @param {number[]} table 既定 [0,0,0] (table[0]+1 が buf[11] に入る)
|
|
121
|
+
* @returns {number[]} 16 byte
|
|
122
|
+
*/
|
|
123
|
+
buildKeyData(prefixCodeArray, code, table) {
|
|
124
|
+
const indexTable = [...table];
|
|
125
|
+
const buf = [];
|
|
126
|
+
|
|
127
|
+
// prefix 2 byte → buf[0],buf[1]
|
|
128
|
+
buf.push(...prefixCodeArray);
|
|
129
|
+
|
|
130
|
+
// code 16bit (ビッグエンディアン) → buf[2]=上位, buf[3]=下位
|
|
131
|
+
const [firstPart, secondPart] = this.decimalToTwoHexInts(code);
|
|
132
|
+
buf.push(firstPart, secondPart);
|
|
133
|
+
|
|
134
|
+
// 0 を 7 個 → buf[4..10]
|
|
135
|
+
buf.push(...new Array(7).fill(0));
|
|
136
|
+
|
|
137
|
+
// index table: indexTable[0] = (table[0]+1)&0xff → buf[11..13]
|
|
138
|
+
indexTable[0] = (table[0] + 1) & 0xff;
|
|
139
|
+
buf.push(...indexTable);
|
|
140
|
+
|
|
141
|
+
// 終端マーカー → buf[14]=0xff, buf[15]=0
|
|
142
|
+
buf.push(0xff, 0);
|
|
143
|
+
|
|
144
|
+
return buf;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 数値を 16bit ビッグエンディアンの 2 byte に分割。
|
|
149
|
+
* vendor: HXDCommandProcessor.js:73-77。
|
|
150
|
+
* @param {number} number
|
|
151
|
+
* @returns {[number, number]} [上位 byte, 下位 byte]
|
|
152
|
+
*/
|
|
153
|
+
decimalToTwoHexInts(number) {
|
|
154
|
+
const firstPart = Math.floor(number / 256);
|
|
155
|
+
const secondPart = number % 256;
|
|
156
|
+
return [firstPart, secondPart];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* byte 配列を大文字 HEX 文字列に変換 (区切り無し・各 byte 2 桁 0 埋め)。
|
|
161
|
+
* これが sendIR の command フィールドに入る文字列。
|
|
162
|
+
* vendor: HXDCommandProcessor.js:132-134。
|
|
163
|
+
* @param {number[]} byteArray
|
|
164
|
+
* @returns {string} 例 "30010000..."
|
|
165
|
+
*/
|
|
166
|
+
toHexString(byteArray) {
|
|
167
|
+
return byteArray.map((byte) => byte.toString(16).padStart(2, "0").toUpperCase()).join("");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* HEX 文字列を byte 配列に戻す。
|
|
172
|
+
* vendor: HXDCommandProcessor.js:124-130。
|
|
173
|
+
* @param {string} hexString
|
|
174
|
+
* @returns {number[]}
|
|
175
|
+
*/
|
|
176
|
+
hexStringToByteArray(hexString) {
|
|
177
|
+
const result = [];
|
|
178
|
+
for (let i = 0; i < hexString.length; i += 2) {
|
|
179
|
+
result.push(parseInt(hexString.substr(i, 2), 16));
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 保存済みエアコン command HEX から状態を復元 (発射には不要・state 復元用)。
|
|
186
|
+
* vendor: HXDCommandProcessor.js:84-117。
|
|
187
|
+
* 前提: length>=22, bytes[0]===0x30 && bytes[1]===0x01。不正時は null。
|
|
188
|
+
* @param {string} hexString
|
|
189
|
+
* @returns {{temperature:number,fanSpeed:number,windDirection:number,autoWindDirection:number,power:number,key:number,mode:number}|null}
|
|
190
|
+
*/
|
|
191
|
+
parseAirCommand(hexString) {
|
|
192
|
+
if (!hexString || hexString.length < 22) return null;
|
|
193
|
+
const bytes = this.hexStringToByteArray(hexString);
|
|
194
|
+
if (bytes.length < 11 || bytes[0] !== 0x30 || bytes[1] !== 0x01) return null;
|
|
195
|
+
return {
|
|
196
|
+
temperature: bytes[4],
|
|
197
|
+
fanSpeed: bytes[5],
|
|
198
|
+
windDirection: bytes[6],
|
|
199
|
+
autoWindDirection: bytes[7],
|
|
200
|
+
power: bytes[8],
|
|
201
|
+
key: bytes[9],
|
|
202
|
+
mode: bytes[10],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- setter (チェーン可・vendor:136-176) ---
|
|
207
|
+
setPower(power) { this.power = power; return this; }
|
|
208
|
+
setTemperature(temperature) { this.temperature = temperature; return this; }
|
|
209
|
+
setModel(model) { this.mode = model; return this; } // vendor 名は setModel (mode を設定)
|
|
210
|
+
setFanSpeed(fanSpeed) { this.fanSpeed = fanSpeed; return this; }
|
|
211
|
+
setWindDirection(windDirection) { this.windDirection = windDirection; return this; }
|
|
212
|
+
setAutoWindDirection(autoWindDirection) { this.autoWindDirection = autoWindDirection; return this; }
|
|
213
|
+
setKey(key) { this.key = key; return this; }
|
|
214
|
+
setCode(code) { this.code = code; return this; }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// =====================================================================
|
|
218
|
+
// B. HXDParametersSwapper (vendor: HXDParametersSwapper.js)
|
|
219
|
+
// UI 値 ↔ HXD 実値変換 + device type 別 key テーブル。
|
|
220
|
+
// =====================================================================
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* HXD パラメータ変換器。biz3 HXDParametersSwapper.js を 1:1 移植。
|
|
224
|
+
* key テーブルは vendor の値・default フォールバックまで完全一致させている。
|
|
225
|
+
*/
|
|
226
|
+
export class HXDParametersSwapper {
|
|
227
|
+
/**
|
|
228
|
+
* エアコン key (HXDParametersSwapper.js:4-17)。
|
|
229
|
+
* 注: UI type 'POWER_ON'/'TEMP_ADD' 等は keyMap に無く default 0x01 (ファイル冒頭トラップ参照)。
|
|
230
|
+
* @param {string} type
|
|
231
|
+
* @returns {number}
|
|
232
|
+
*/
|
|
233
|
+
getAirKey(type) {
|
|
234
|
+
const keyMap = {
|
|
235
|
+
POWER_STATUS_ON: 0x01,
|
|
236
|
+
POWER_STATUS_OFF: 0x01,
|
|
237
|
+
TEMP_CONTROL_ADD: 0x06,
|
|
238
|
+
TEMP_CONTROL_REDUCE: 0x07,
|
|
239
|
+
MODE: 0x02,
|
|
240
|
+
FAN_SPEED: 0x03,
|
|
241
|
+
WIND_DIRECTION: 0x04,
|
|
242
|
+
AUTO_WIND_DIRECTION: 0x05,
|
|
243
|
+
};
|
|
244
|
+
return keyMap[type] || 0x01;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* mode index → HXD 値 (vendor:45-54)。{0:自動,1:制冷,2:除湿,3:送風,4:制熱}
|
|
249
|
+
* @param {number} index @returns {number}
|
|
250
|
+
*/
|
|
251
|
+
getModeValue(index) {
|
|
252
|
+
const valueMap = { 0: 0x01, 1: 0x02, 2: 0x03, 3: 0x04, 4: 0x05 };
|
|
253
|
+
return valueMap[index] || 0x01;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* fanSpeed index → HXD 値 (vendor:67-75)。{0:自動,1:低,2:中,3:高}
|
|
258
|
+
* @param {number} index @returns {number}
|
|
259
|
+
*/
|
|
260
|
+
getFanSpeedValue(index) {
|
|
261
|
+
const valueMap = { 0: 0x01, 1: 0x02, 2: 0x03, 3: 0x04 };
|
|
262
|
+
return valueMap[index] || 0x01;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* windDirection index → HXD 値 (vendor:87-94)。{0:上,1:中,2:下}。default 0x02。
|
|
267
|
+
* @param {number} index @returns {number}
|
|
268
|
+
*/
|
|
269
|
+
getWindDirectionValue(index) {
|
|
270
|
+
const valueMap = { 0: 0x01, 1: 0x02, 2: 0x03 };
|
|
271
|
+
return valueMap[index] || 0x02;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* ライト key (vendor:114-125)。
|
|
276
|
+
* @param {string} type @returns {number}
|
|
277
|
+
*/
|
|
278
|
+
getLightKey(type) {
|
|
279
|
+
const keyMap = {
|
|
280
|
+
POWER_STATUS_ON: 0x01,
|
|
281
|
+
POWER_STATUS_OFF: 0x02,
|
|
282
|
+
MODE: 0x05,
|
|
283
|
+
BRIGHTNESS_UP: 0x03,
|
|
284
|
+
BRIGHTNESS_DOWN: 0x04,
|
|
285
|
+
COLOR_TEMP_UP: 0x09,
|
|
286
|
+
COLOR_TEMP_DOWN: 0x0a,
|
|
287
|
+
};
|
|
288
|
+
return keyMap[type] || 0x01;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* TV key (vendor:128-147)。
|
|
293
|
+
* @param {string} type @returns {number}
|
|
294
|
+
*/
|
|
295
|
+
getTVKey(type) {
|
|
296
|
+
const keyMap = {
|
|
297
|
+
POWER_STATUS_ON: 0x06,
|
|
298
|
+
POWER_STATUS_OFF: 0x06,
|
|
299
|
+
MUTE: 0x07,
|
|
300
|
+
BACK: 0x14,
|
|
301
|
+
UP: 0x16,
|
|
302
|
+
MENU: 0x03,
|
|
303
|
+
LEFT: 0x17,
|
|
304
|
+
OK: 0x15,
|
|
305
|
+
RIGHT: 0x18,
|
|
306
|
+
VOLUME_UP: 0x05,
|
|
307
|
+
DOWN: 0x19,
|
|
308
|
+
CHANNEL_UP: 0x02,
|
|
309
|
+
VOLUME_DOWN: 0x01,
|
|
310
|
+
HOME: 0x1a,
|
|
311
|
+
CHANNEL_DOWN: 0x04,
|
|
312
|
+
};
|
|
313
|
+
return keyMap[type] || 0x01;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 扇風機 key (vendor:150-162)。
|
|
318
|
+
* @param {string} type @returns {number}
|
|
319
|
+
*/
|
|
320
|
+
getFanKey(type) {
|
|
321
|
+
const keyMap = {
|
|
322
|
+
POWER_STATUS_ON: 0x01,
|
|
323
|
+
POWER_STATUS_OFF: 0x01,
|
|
324
|
+
FAN_SPEED: 0x02,
|
|
325
|
+
SHAKE_HEAD: 0x03,
|
|
326
|
+
MODE: 0x04,
|
|
327
|
+
LOW: 0x14,
|
|
328
|
+
MIDDLE: 0x15,
|
|
329
|
+
HIGH: 0x16,
|
|
330
|
+
};
|
|
331
|
+
return keyMap[type] || 0x01;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* device type (irType) 別に key を引く (非エアコン UI 経由)。
|
|
336
|
+
* vendor: HXDParametersSwapper.js:166-180。
|
|
337
|
+
* 未知 type は warn を出さず default 0x01 (CLI なので console.warn は省略)。
|
|
338
|
+
* @param {number} irType IR_TYPE のいずれか
|
|
339
|
+
* @param {string} type ボタン種別文字列
|
|
340
|
+
* @returns {number}
|
|
341
|
+
*/
|
|
342
|
+
getKeyByDeviceType(irType, type) {
|
|
343
|
+
switch (irType) {
|
|
344
|
+
case 0xc000:
|
|
345
|
+
return this.getAirKey(type);
|
|
346
|
+
case 0xe000:
|
|
347
|
+
return this.getLightKey(type);
|
|
348
|
+
case 0x2000:
|
|
349
|
+
return this.getTVKey(type);
|
|
350
|
+
case 0x8000:
|
|
351
|
+
return this.getFanKey(type);
|
|
352
|
+
default:
|
|
353
|
+
return 0x01;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// =====================================================================
|
|
359
|
+
// C. 高レベル command 生成 (純関数・biz3 呼び出しフローを再現)
|
|
360
|
+
// =====================================================================
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* エアコンの発射 command (HEX 文字列) を生成。
|
|
364
|
+
* biz3 buildCommand を再現 (remote-air/index.js:117-138, 呼び出しフロー extraLogic D)。
|
|
365
|
+
*
|
|
366
|
+
* 状態は buildAirCommand が buf[4..10] へ直接書き込むため、key 値は発射にほぼ影響しない
|
|
367
|
+
* (key は buf[9] に入るが、power は buf[8] が支配的)。keyType 未指定時は default 0x01。
|
|
368
|
+
*
|
|
369
|
+
* @param {{
|
|
370
|
+
* code: number, // remote.code (プリセット DB 由来の数値)
|
|
371
|
+
* power?: boolean, // 電源 ON/OFF (remote-air:126: power?0x01:0x00)
|
|
372
|
+
* temperature?: number, // UI 温度値 (16-32, 変換なしでそのまま)
|
|
373
|
+
* mode?: number, // mode index 0-4 (getModeValue で HXD 値に変換)
|
|
374
|
+
* fanSpeed?: number, // fanSpeed index 0-3
|
|
375
|
+
* windDirection?: number, // windDirection index 0-2
|
|
376
|
+
* autoSwing?: boolean, // 自動風向 (remote-air:131: autoSwing?0x01:0x00)
|
|
377
|
+
* keyType?: string, // getAirKey に渡す type (省略時 buf[9]=0x01)
|
|
378
|
+
* }} state
|
|
379
|
+
* @returns {string} 大文字 HEX command 文字列
|
|
380
|
+
*/
|
|
381
|
+
export function buildAirCommandHex(state) {
|
|
382
|
+
const swapper = new HXDParametersSwapper();
|
|
383
|
+
const proc = new HXDCommandProcessor();
|
|
384
|
+
const key = swapper.getAirKey(state.keyType);
|
|
385
|
+
const cmd = proc
|
|
386
|
+
.setKey(key)
|
|
387
|
+
.setCode(state.code)
|
|
388
|
+
.setPower(state.power ? 0x01 : 0x00)
|
|
389
|
+
.setTemperature(state.temperature ?? 25)
|
|
390
|
+
.setModel(swapper.getModeValue(state.mode ?? 0))
|
|
391
|
+
.setFanSpeed(swapper.getFanSpeedValue(state.fanSpeed ?? 0))
|
|
392
|
+
.setWindDirection(swapper.getWindDirectionValue(state.windDirection ?? 1))
|
|
393
|
+
.setAutoWindDirection(state.autoSwing ? 0x01 : 0x00)
|
|
394
|
+
.buildAirCommand();
|
|
395
|
+
return proc.toHexString(cmd);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* 非エアコン (TV/ライト/扇風機) の発射 command (HEX 文字列) を生成。
|
|
400
|
+
* biz3 buildCommand を再現 (remote-non-air/index.js:113-124, 呼び出しフロー extraLogic D)。
|
|
401
|
+
*
|
|
402
|
+
* @param {{
|
|
403
|
+
* irType: number, // IR_TYPE.TV / LIGHT / FAN (getKeyByDeviceType に渡す)
|
|
404
|
+
* code: number, // remote.code
|
|
405
|
+
* buttonType: string, // ボタン種別 (getTVKey/getLightKey/getFanKey の keyMap キー)
|
|
406
|
+
* }} p
|
|
407
|
+
* @returns {string} 大文字 HEX command 文字列
|
|
408
|
+
*/
|
|
409
|
+
export function buildNonAirCommandHex({ irType, code, buttonType }) {
|
|
410
|
+
const swapper = new HXDParametersSwapper();
|
|
411
|
+
const proc = new HXDCommandProcessor();
|
|
412
|
+
const key = swapper.getKeyByDeviceType(irType, buttonType);
|
|
413
|
+
const cmd = proc.setKey(key).setCode(code).buildNonAirCommand();
|
|
414
|
+
return proc.toHexString(cmd);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// =====================================================================
|
|
418
|
+
// D. sendIR (remoteEmit) — WS 発射 op
|
|
419
|
+
// =====================================================================
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* 既に生成した HEX command をそのまま Hub3 に発射する。
|
|
423
|
+
* frame は learnIR 等の sendIR と完全共通 (useRemoteCtrl.js:460-484)。
|
|
424
|
+
*
|
|
425
|
+
* フィールド名トラップ:
|
|
426
|
+
* - deviceId : Hub3 の deviceUUID 文字列 (hub3DeviceId ではなく **deviceId**)
|
|
427
|
+
* - irDeviceUUID: 保存済みリモコンの uuid。未保存プリセットでは **空文字 ''**
|
|
428
|
+
* (remote-air:369 / remote-non-air:155 で remote.uuid || '')
|
|
429
|
+
*
|
|
430
|
+
* 応答: action:'biz3IRRemote', op:'sendIR', success:bool, message?, data?
|
|
431
|
+
* (handleRemoteResponse:65-80 は op==='sendIR' && success で成功扱い)
|
|
432
|
+
*
|
|
433
|
+
* @param {import("./transport.js").Hub3WsClient} client
|
|
434
|
+
* @param {{
|
|
435
|
+
* deviceId: string, // Hub3 deviceUUID
|
|
436
|
+
* command: string, // HEX command (buildAir/NonAirCommandHex の戻り値)
|
|
437
|
+
* irType: number, // remote.type 実値 (IR_TYPE)
|
|
438
|
+
* companyID: string, // gStripe.customerInfo.companyID
|
|
439
|
+
* irDeviceUUID?: string, // remote.uuid (未保存は '')
|
|
440
|
+
* operation?: string, // 既定 'remoteEmit'
|
|
441
|
+
* timeoutMs?: number,
|
|
442
|
+
* }} p
|
|
443
|
+
* @returns {Promise<object>} 応答メッセージ (success / data / message)
|
|
444
|
+
*/
|
|
445
|
+
export async function sendIR(client, p) {
|
|
446
|
+
if (!p || !p.deviceId) throw new Error("deviceId required (Hub3 deviceUUID)");
|
|
447
|
+
if (!p.command) throw new Error("command required (HEX 文字列)");
|
|
448
|
+
if (p.irType == null) throw new Error("irType required (remote.type 実値)");
|
|
449
|
+
if (!p.companyID) throw new Error("companyID required");
|
|
450
|
+
|
|
451
|
+
const frame = {
|
|
452
|
+
action: ACTION,
|
|
453
|
+
op: "sendIR",
|
|
454
|
+
deviceId: p.deviceId,
|
|
455
|
+
command: p.command,
|
|
456
|
+
operation: p.operation ?? "remoteEmit",
|
|
457
|
+
irType: p.irType,
|
|
458
|
+
companyID: p.companyID,
|
|
459
|
+
irDeviceUUID: p.irDeviceUUID ?? "",
|
|
460
|
+
};
|
|
461
|
+
const resp = await client.request(frame, p.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
462
|
+
// ir.js/biz3 の sendIR と同じく success===true を要求する (strict)。
|
|
463
|
+
assertSuccess(resp, "sendIR", { strict: true });
|
|
464
|
+
return resp;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* エアコン: 状態から command を生成してそのまま発射する複合関数。
|
|
469
|
+
* @param {import("./transport.js").Hub3WsClient} client
|
|
470
|
+
* @param {{
|
|
471
|
+
* deviceId: string, companyID: string, code: number,
|
|
472
|
+
* irDeviceUUID?: string, timeoutMs?: number,
|
|
473
|
+
* power?: boolean, temperature?: number, mode?: number,
|
|
474
|
+
* fanSpeed?: number, windDirection?: number, autoSwing?: boolean, keyType?: string,
|
|
475
|
+
* }} p
|
|
476
|
+
* @returns {Promise<{command:string, response:object}>}
|
|
477
|
+
*/
|
|
478
|
+
export async function emitAir(client, p) {
|
|
479
|
+
const command = buildAirCommandHex(p);
|
|
480
|
+
const response = await sendIR(client, {
|
|
481
|
+
deviceId: p.deviceId,
|
|
482
|
+
command,
|
|
483
|
+
irType: IR_TYPE.AIR,
|
|
484
|
+
companyID: p.companyID,
|
|
485
|
+
irDeviceUUID: p.irDeviceUUID ?? "",
|
|
486
|
+
timeoutMs: p.timeoutMs,
|
|
487
|
+
});
|
|
488
|
+
return { command, response };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 非エアコン (TV/ライト/扇風機): ボタン押下を生成して発射する複合関数。
|
|
493
|
+
* @param {import("./transport.js").Hub3WsClient} client
|
|
494
|
+
* @param {{
|
|
495
|
+
* deviceId: string, companyID: string, code: number,
|
|
496
|
+
* irType: number, buttonType: string,
|
|
497
|
+
* irDeviceUUID?: string, timeoutMs?: number,
|
|
498
|
+
* }} p
|
|
499
|
+
* @returns {Promise<{command:string, response:object}>}
|
|
500
|
+
*/
|
|
501
|
+
export async function emitButton(client, p) {
|
|
502
|
+
const command = buildNonAirCommandHex({
|
|
503
|
+
irType: p.irType,
|
|
504
|
+
code: p.code,
|
|
505
|
+
buttonType: p.buttonType,
|
|
506
|
+
});
|
|
507
|
+
const response = await sendIR(client, {
|
|
508
|
+
deviceId: p.deviceId,
|
|
509
|
+
command,
|
|
510
|
+
irType: p.irType,
|
|
511
|
+
companyID: p.companyID,
|
|
512
|
+
irDeviceUUID: p.irDeviceUUID ?? "",
|
|
513
|
+
timeoutMs: p.timeoutMs,
|
|
514
|
+
});
|
|
515
|
+
return { command, response };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* namespace (hub.presetir.*) に露出する client op の allowlist。
|
|
520
|
+
* HXDCommandProcessor / HXDParametersSwapper (class) と buildAirCommandHex /
|
|
521
|
+
* buildNonAirCommandHex (client を取らない純ビルダ) は namespace に出さない
|
|
522
|
+
* (client.js _bindNs が ws を第1引数に注入して壊れるため)。これらは低レベル
|
|
523
|
+
* ユーティリティとして index.js の presetir namespace から直接 import して使う。
|
|
524
|
+
*/
|
|
525
|
+
export const NAMESPACE_OPS = ["sendIR", "emitAir", "emitButton"];
|
package/src/prompts.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// 対話 UI ヘルパ。矢印キー選択・テキスト入力・確認は @inquirer/prompts に委譲する
|
|
2
|
+
// (番号入力より UX が良く、ページング・検索・キャンセルも標準で効く)。
|
|
3
|
+
//
|
|
4
|
+
// 設計方針:
|
|
5
|
+
// - `--json` 指定時や非 TTY (パイプ越し / cron) では呼ばない (呼び出し側で isInteractive 判定)。
|
|
6
|
+
// - 公開 API (selectFromList / menu / promptText / confirm / isInteractive) は据え置き、
|
|
7
|
+
// 中身だけ inquirer 化。selectFromList は要素 1 個なら auto-pick、空なら throw を維持。
|
|
8
|
+
|
|
9
|
+
import { select, input, confirm as inquirerConfirm } from "@inquirer/prompts";
|
|
10
|
+
|
|
11
|
+
export function isInteractive() {
|
|
12
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 先頭の "? " などの装飾を除いた素のメッセージ (inquirer が "? " を自前で付けるため)。 */
|
|
16
|
+
function plainMessage(m) {
|
|
17
|
+
return String(m).replace(/^[?>\s]+/, "").trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* テキスト入力 prompt。空 OK なら required=false で。
|
|
22
|
+
* @param {string} message
|
|
23
|
+
* @param {{required?:boolean, defaultValue?:string|null}} [opts]
|
|
24
|
+
*/
|
|
25
|
+
export async function promptText(message, { required = true, defaultValue = null } = {}) {
|
|
26
|
+
return input({
|
|
27
|
+
message: plainMessage(message),
|
|
28
|
+
default: defaultValue != null ? String(defaultValue) : undefined,
|
|
29
|
+
validate: (v) => (required && defaultValue == null && !String(v).trim() ? "必須項目です" : true),
|
|
30
|
+
}).then((v) => String(v).trim());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* yes/no 確認。デフォルトは defaultYes。
|
|
35
|
+
* @param {string} message
|
|
36
|
+
* @param {{defaultYes?:boolean}} [opts]
|
|
37
|
+
*/
|
|
38
|
+
export async function confirm(message, { defaultYes = true } = {}) {
|
|
39
|
+
return inquirerConfirm({ message: plainMessage(message), default: defaultYes });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* リストから 1 つ選ばせる (↑↓ で移動 / Enter で決定)。要素 1 個なら auto-pick。空ならエラー。
|
|
44
|
+
*
|
|
45
|
+
* @template T
|
|
46
|
+
* @param {string} message
|
|
47
|
+
* @param {T[]} items
|
|
48
|
+
* @param {(item:T) => string} [getLabel] 表示ラベル (default: String)
|
|
49
|
+
* @returns {Promise<T>}
|
|
50
|
+
*/
|
|
51
|
+
export async function selectFromList(message, items, getLabel = String) {
|
|
52
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
53
|
+
throw new Error(`${message}: 候補がありません`);
|
|
54
|
+
}
|
|
55
|
+
if (items.length === 1) return items[0];
|
|
56
|
+
|
|
57
|
+
return select({
|
|
58
|
+
message: plainMessage(message),
|
|
59
|
+
choices: items.map((it) => ({ name: getLabel(it), value: it })),
|
|
60
|
+
pageSize: 12, // これを超えると ↑↓ でスクロール
|
|
61
|
+
loop: false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* メニュー (selectFromList の薄いラッパ、ラベルだけ表示)。戻り値は選ばれたエントリの `value`。
|
|
67
|
+
*
|
|
68
|
+
* @param {string} title
|
|
69
|
+
* @param {{label:string, value:any}[]} entries
|
|
70
|
+
*/
|
|
71
|
+
export async function menu(title, entries) {
|
|
72
|
+
const chosen = await selectFromList(title, entries, (e) => e.label);
|
|
73
|
+
return chosen.value;
|
|
74
|
+
}
|