odelic 0.2.0 → 0.2.2
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/README.md +93 -91
- package/bin/odelic-mcp.js +1 -1
- package/bin/odelic.js +1 -1
- package/dist/cli.d.ts +58 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +279 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +140 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +448 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +139 -0
- package/dist/config.js.map +1 -0
- package/dist/controller.d.ts +95 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +239 -0
- package/dist/controller.js.map +1 -0
- package/dist/crypto.d.ts +34 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +111 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/{src → dist}/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +18 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +21 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp.d.ts +23 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +74 -0
- package/dist/mcp.js.map +1 -0
- package/dist/protocol.d.ts +90 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +178 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +187 -0
- package/dist/server.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +19 -7
- package/src/{cli.js → cli.ts} +64 -50
- package/src/client.ts +497 -0
- package/src/{config.js → config.ts} +37 -23
- package/src/controller.ts +294 -0
- package/src/{crypto.js → crypto.ts} +46 -28
- package/src/{errors.js → errors.ts} +2 -2
- package/src/index.ts +17 -0
- package/src/log.ts +24 -0
- package/src/{mcp.js → mcp.ts} +31 -20
- package/src/{protocol.js → protocol.ts} +56 -30
- package/src/{server.js → server.ts} +57 -38
- package/src/version.ts +1 -0
- package/src/client.js +0 -362
- package/src/controller.js +0 -219
- package/src/log.js +0 -20
- package/src/version.js +0 -1
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as P from './protocol.ts';
|
|
2
|
+
export declare const ALL_NAMES: readonly string[];
|
|
3
|
+
export declare const DEFAULT_IDLE_TIMEOUT = 60;
|
|
4
|
+
/** The JSON stored on disk (snake_case, shared with the Python package). */
|
|
5
|
+
export interface ConfigData {
|
|
6
|
+
id?: string | null;
|
|
7
|
+
node?: string | null;
|
|
8
|
+
idle_timeout?: number | string | null;
|
|
9
|
+
lights?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/** Seconds before an idle connection is closed; 0 (or "never"/"keep") = stay connected; null = not set. */
|
|
12
|
+
export declare function parseIdle(value: unknown): number | null;
|
|
13
|
+
export declare function configPath(): string;
|
|
14
|
+
export declare class Config {
|
|
15
|
+
path: string;
|
|
16
|
+
id: string | null;
|
|
17
|
+
node: string | null;
|
|
18
|
+
idleTimeout: number | null;
|
|
19
|
+
/** name -> vaddr hex (null prototype: names like "constructor" are plain keys) */
|
|
20
|
+
lights: Record<string, string>;
|
|
21
|
+
constructor(data?: ConfigData | null, file?: string | null);
|
|
22
|
+
static load(file?: string | null): Config;
|
|
23
|
+
save(): void;
|
|
24
|
+
/** Effective app ID: override > $ODELIC_ID > config. */
|
|
25
|
+
appId(override?: string | null): string | null;
|
|
26
|
+
/** Effective idle timeout: override > $ODELIC_IDLE_TIMEOUT > config > 60 s. 0 = stay connected. */
|
|
27
|
+
idle(override?: number | string | null): number;
|
|
28
|
+
nameOf(vaddr: Uint8Array): string | null;
|
|
29
|
+
/** light name / "all" / vaddr hex / bytes -> 4-byte vaddr */
|
|
30
|
+
resolve(target: P.VaddrLike | null | undefined): Buffer;
|
|
31
|
+
setName(name: string, target: P.VaddrLike): void;
|
|
32
|
+
removeName(name: string): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,CAAC,MAAM,eAAe,CAAC;AAEnC,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAA8B,CAAC;AACtE,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAGvC,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,2GAA2G;AAC3G,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAOvD;AAED,wBAAgB,UAAU,IAAI,MAAM,CAInC;AAMD,qBAAa,MAAM;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE/B,YAAY,IAAI,GAAE,UAAU,GAAG,IAAS,EAAE,IAAI,GAAE,MAAM,GAAG,IAAW,EAQnE;IAED,MAAM,CAAC,IAAI,CAAC,IAAI,GAAE,MAAM,GAAG,IAAW,GAAG,MAAM,CAS9C;IAED,IAAI,IAAI,IAAI,CAYX;IAED,wDAAwD;IACxD,KAAK,CAAC,QAAQ,GAAE,MAAM,GAAG,IAAW,GAAG,MAAM,GAAG,IAAI,CAEnD;IAED,mGAAmG;IACnG,IAAI,CAAC,QAAQ,GAAE,MAAM,GAAG,MAAM,GAAG,IAAW,GAAG,MAAM,CAMpD;IAGD,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAIvC;IAED,6DAA6D;IAC7D,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CActD;IAED,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,IAAI,CAI/C;IAED,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAG7B;CACF"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User configuration: app ID, preferred node, connection mode and light names.
|
|
3
|
+
*
|
|
4
|
+
* Stored as JSON at $ODELIC_CONFIG, or $XDG_CONFIG_HOME/odelic/config.json (default ~/.config/odelic/config.json).
|
|
5
|
+
* The format is shared with the Python package (odelic.py), so both can use the same file:
|
|
6
|
+
*
|
|
7
|
+
* {"id": "12345678", "node": null, "idle_timeout": 60, "lights": {"living": "00000000"}}
|
|
8
|
+
*
|
|
9
|
+
* idle_timeout: seconds of inactivity before the BLE link is released (so the phone app can connect);
|
|
10
|
+
* 0 keeps the link open permanently.
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { InputError } from './errors.js';
|
|
16
|
+
import * as P from './protocol.js';
|
|
17
|
+
export const ALL_NAMES = ['all', '*', '全部', 'すべて'];
|
|
18
|
+
export const DEFAULT_IDLE_TIMEOUT = 60;
|
|
19
|
+
const KEEP_WORDS = ['0', 'never', 'none', 'off', 'keep', 'forever'];
|
|
20
|
+
/** Seconds before an idle connection is closed; 0 (or "never"/"keep") = stay connected; null = not set. */
|
|
21
|
+
export function parseIdle(value) {
|
|
22
|
+
if (value === null || value === undefined || value === '')
|
|
23
|
+
return null;
|
|
24
|
+
if (typeof value === 'string' && KEEP_WORDS.includes(value.trim().toLowerCase()))
|
|
25
|
+
return 0;
|
|
26
|
+
const v = typeof value === 'string' ? Number(value.trim()) : Number(value);
|
|
27
|
+
if (typeof value === 'boolean' || !Number.isFinite(v))
|
|
28
|
+
throw new InputError(`invalid idle timeout: ${String(value)}`);
|
|
29
|
+
if (v < 0)
|
|
30
|
+
throw new InputError('idle timeout must be >= 0');
|
|
31
|
+
return v;
|
|
32
|
+
}
|
|
33
|
+
export function configPath() {
|
|
34
|
+
if (process.env.ODELIC_CONFIG)
|
|
35
|
+
return expandHome(process.env.ODELIC_CONFIG);
|
|
36
|
+
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
37
|
+
return path.join(base, 'odelic', 'config.json');
|
|
38
|
+
}
|
|
39
|
+
function expandHome(p) {
|
|
40
|
+
return p === '~' || p.startsWith('~/') ? path.join(os.homedir(), p.slice(1)) : p;
|
|
41
|
+
}
|
|
42
|
+
export class Config {
|
|
43
|
+
path;
|
|
44
|
+
id;
|
|
45
|
+
node;
|
|
46
|
+
idleTimeout;
|
|
47
|
+
/** name -> vaddr hex (null prototype: names like "constructor" are plain keys) */
|
|
48
|
+
lights;
|
|
49
|
+
constructor(data = {}, file = null) {
|
|
50
|
+
data ??= {};
|
|
51
|
+
this.path = file ?? configPath();
|
|
52
|
+
this.id = data.id ?? null;
|
|
53
|
+
this.node = data.node ?? null;
|
|
54
|
+
this.lights = Object.create(null);
|
|
55
|
+
for (const [k, v] of Object.entries(data.lights ?? {}))
|
|
56
|
+
this.lights[String(k)] = String(v).toLowerCase();
|
|
57
|
+
this.idleTimeout = parseIdle(data.idle_timeout);
|
|
58
|
+
}
|
|
59
|
+
static load(file = null) {
|
|
60
|
+
const p = file ?? configPath();
|
|
61
|
+
let data = {};
|
|
62
|
+
try {
|
|
63
|
+
data = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
if (e.code !== 'ENOENT')
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
return new Config(data, p);
|
|
70
|
+
}
|
|
71
|
+
save() {
|
|
72
|
+
fs.mkdirSync(path.dirname(this.path), { recursive: true });
|
|
73
|
+
const tmp = this.path.replace(/\.[^./]*$/, '') + '.tmp';
|
|
74
|
+
const data = { id: this.id, node: this.node, idle_timeout: this.idleTimeout, lights: this.lights };
|
|
75
|
+
// the ID is the mesh password: keep the file private
|
|
76
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
|
|
77
|
+
try {
|
|
78
|
+
fs.chmodSync(tmp, 0o600);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// not supported on this filesystem
|
|
82
|
+
}
|
|
83
|
+
fs.renameSync(tmp, this.path);
|
|
84
|
+
}
|
|
85
|
+
/** Effective app ID: override > $ODELIC_ID > config. */
|
|
86
|
+
appId(override = null) {
|
|
87
|
+
return override || process.env.ODELIC_ID || this.id;
|
|
88
|
+
}
|
|
89
|
+
/** Effective idle timeout: override > $ODELIC_IDLE_TIMEOUT > config > 60 s. 0 = stay connected. */
|
|
90
|
+
idle(override = null) {
|
|
91
|
+
for (const v of [override, process.env.ODELIC_IDLE_TIMEOUT, this.idleTimeout]) {
|
|
92
|
+
const parsed = parseIdle(v);
|
|
93
|
+
if (parsed !== null)
|
|
94
|
+
return parsed;
|
|
95
|
+
}
|
|
96
|
+
return DEFAULT_IDLE_TIMEOUT;
|
|
97
|
+
}
|
|
98
|
+
// ------------------------------------------------------------ names
|
|
99
|
+
nameOf(vaddr) {
|
|
100
|
+
const hex = Buffer.from(vaddr).toString('hex');
|
|
101
|
+
for (const [name, v] of Object.entries(this.lights))
|
|
102
|
+
if (v === hex)
|
|
103
|
+
return name;
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
/** light name / "all" / vaddr hex / bytes -> 4-byte vaddr */
|
|
107
|
+
resolve(target) {
|
|
108
|
+
if (target === null || target === undefined)
|
|
109
|
+
return P.BROADCAST;
|
|
110
|
+
if (target instanceof Uint8Array)
|
|
111
|
+
return P.vaddr(target);
|
|
112
|
+
const t = String(target).trim();
|
|
113
|
+
if (t === '' || ALL_NAMES.includes(t.toLowerCase()))
|
|
114
|
+
return P.BROADCAST;
|
|
115
|
+
for (const [name, v] of Object.entries(this.lights)) {
|
|
116
|
+
if (name.toLowerCase() === t.toLowerCase())
|
|
117
|
+
return Buffer.from(v, 'hex');
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
return P.vaddr(t);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
const known = Object.keys(this.lights).join(', ') || '(none)';
|
|
124
|
+
throw new InputError(`unknown light ${JSON.stringify(String(target))} (known names: ${known})`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
setName(name, target) {
|
|
128
|
+
const v = this.resolve(target);
|
|
129
|
+
if (v.equals(P.BROADCAST))
|
|
130
|
+
throw new InputError('cannot name the broadcast address');
|
|
131
|
+
this.lights[name] = v.toString('hex');
|
|
132
|
+
}
|
|
133
|
+
removeName(name) {
|
|
134
|
+
if (!Object.hasOwn(this.lights, name))
|
|
135
|
+
throw new InputError(`no light named ${JSON.stringify(name)}`);
|
|
136
|
+
delete this.lights[name];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,CAAC,MAAM,eAAe,CAAC;AAEnC,MAAM,CAAC,MAAM,SAAS,GAAsB,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAUpE,2GAA2G;AAC3G,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3F,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3E,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtH,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,2BAA2B,CAAC,CAAC;IAC7D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa;QAAE,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC/E,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,OAAO,MAAM;IACjB,IAAI,CAAS;IACb,EAAE,CAAgB;IAClB,IAAI,CAAgB;IACpB,WAAW,CAAgB;IAC3B,kFAAkF;IAClF,MAAM,CAAyB;IAE/B,YAAY,IAAI,GAAsB,EAAE,EAAE,IAAI,GAAkB,IAAI;QAClE,IAAI,KAAK,EAAE,CAAC;QACZ,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA2B,CAAC;QAC5D,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACzG,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,IAAI,GAAkB,IAAI;QACpC,MAAM,CAAC,GAAG,IAAI,IAAI,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,GAAe,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAe,CAAC;QAC9D,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI;QACF,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC;QACxD,MAAM,IAAI,GAAe,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QAC/G,qDAAqD;QACrD,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/F,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;QACD,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,QAAQ,GAAkB,IAAI;QAClC,OAAO,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,EAAE,CAAC;IACtD,CAAC;IAED,mGAAmG;IACnG,IAAI,CAAC,QAAQ,GAA2B,IAAI;QAC1C,KAAK,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9E,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,MAAM,CAAC;QACrC,CAAC;QACD,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,qEAAqE;IACrE,MAAM,CAAC,KAAiB;QACtB,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,KAAK,GAAG;gBAAE,OAAO,IAAI,CAAC;QAChF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6DAA6D;IAC7D,OAAO,CAAC,MAAsC;QAC5C,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC,SAAS,CAAC;QAChE,IAAI,MAAM,YAAY,UAAU;YAAE,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK,EAAE,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC,SAAS,CAAC;QACxE,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE;gBAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,CAAC;YACH,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;YAC9D,MAAM,IAAI,UAAU,CAAC,iBAAiB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,kBAAkB,KAAK,GAAG,CAAC,CAAC;QAClG,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAY,EAAE,MAAmB;QACvC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,mCAAmC,CAAC,CAAC;QACrF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,UAAU,CAAC,IAAY;QACrB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,kBAAkB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtG,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as P from './protocol.ts';
|
|
2
|
+
import { OdelicMesh } from './client.ts';
|
|
3
|
+
import { Config } from './config.ts';
|
|
4
|
+
/** Where a command goes: a light name from the config, "all", or a vaddr. */
|
|
5
|
+
export type Target = P.VaddrLike;
|
|
6
|
+
export interface ActionResult {
|
|
7
|
+
ok: true;
|
|
8
|
+
action: string;
|
|
9
|
+
target?: string;
|
|
10
|
+
color?: number;
|
|
11
|
+
brightness?: number;
|
|
12
|
+
level?: number;
|
|
13
|
+
group?: number;
|
|
14
|
+
index?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface LightInfo {
|
|
17
|
+
name: string | null;
|
|
18
|
+
vaddr: string;
|
|
19
|
+
online: boolean;
|
|
20
|
+
mac?: string;
|
|
21
|
+
product?: number;
|
|
22
|
+
version?: string;
|
|
23
|
+
}
|
|
24
|
+
export type LightStatus = ({
|
|
25
|
+
name: string | null;
|
|
26
|
+
online: true;
|
|
27
|
+
} & P.LightStateJSON) | {
|
|
28
|
+
name: string | null;
|
|
29
|
+
vaddr: string;
|
|
30
|
+
online: false;
|
|
31
|
+
};
|
|
32
|
+
export interface ControllerInfo {
|
|
33
|
+
connected: boolean;
|
|
34
|
+
node: string | null;
|
|
35
|
+
mode: 'keep' | 'idle';
|
|
36
|
+
idle_timeout: number;
|
|
37
|
+
}
|
|
38
|
+
export interface ControllerOptions {
|
|
39
|
+
/** BLE name / address of the node to connect through (default: config, else strongest node) */
|
|
40
|
+
node?: string | null;
|
|
41
|
+
config?: Config | null;
|
|
42
|
+
/** seconds before an idle link is released; 0 = stay connected; null = $ODELIC_IDLE_TIMEOUT / config / 60 s */
|
|
43
|
+
idleTimeout?: number | string | null;
|
|
44
|
+
/** use this mesh connection instead of creating one (mainly for tests) */
|
|
45
|
+
mesh?: OdelicMesh | null;
|
|
46
|
+
}
|
|
47
|
+
/** Integer from a number or numeric string (like Python's int()); throws InputError otherwise. */
|
|
48
|
+
export declare function toInt(v: unknown, what?: string): number;
|
|
49
|
+
export declare class Controller {
|
|
50
|
+
readonly config: Config;
|
|
51
|
+
readonly mesh: OdelicMesh;
|
|
52
|
+
/** seconds; 0 = keep the link open */
|
|
53
|
+
readonly idleTimeout: number;
|
|
54
|
+
private queue;
|
|
55
|
+
private idle;
|
|
56
|
+
/** @param appId default: $ODELIC_ID / config file */
|
|
57
|
+
constructor(appId?: string | null, { node, config, idleTimeout, mesh }?: ControllerOptions);
|
|
58
|
+
/** Run `fn` after every previously queued operation (a simple mutex). */
|
|
59
|
+
private locked;
|
|
60
|
+
private run;
|
|
61
|
+
private cancelIdle;
|
|
62
|
+
private armIdle;
|
|
63
|
+
/** Connect now (otherwise the first command connects). */
|
|
64
|
+
connect(): Promise<void>;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
get connected(): boolean;
|
|
67
|
+
get keepConnected(): boolean;
|
|
68
|
+
info(): ControllerInfo;
|
|
69
|
+
private label;
|
|
70
|
+
private done;
|
|
71
|
+
on(target?: Target): Promise<ActionResult>;
|
|
72
|
+
off(target?: Target): Promise<ActionResult>;
|
|
73
|
+
/** color 0 (warm) .. 100 (cool), brightness 1..100 % */
|
|
74
|
+
setLight(target?: Target, color?: number | string, brightness?: number | string): Promise<ActionResult>;
|
|
75
|
+
nightLight(target?: Target, level?: number | string): Promise<ActionResult>;
|
|
76
|
+
/** action: "on", "off" or "light" (uses color / brightness) */
|
|
77
|
+
group(groupId: number | string, action?: string, color?: number | string, brightness?: number | string): Promise<ActionResult>;
|
|
78
|
+
scene(index: number | string, save?: boolean): Promise<ActionResult>;
|
|
79
|
+
changeColor(target: Target): Promise<ActionResult>;
|
|
80
|
+
/** Lights answering a mesh ping, plus named lights that did not answer (online: false). */
|
|
81
|
+
lights(timeout?: number): Promise<LightInfo[]>;
|
|
82
|
+
/** State of one light, or of every responding light for "all". */
|
|
83
|
+
status(target?: Target): Promise<LightStatus | LightStatus[]>;
|
|
84
|
+
private statusOf;
|
|
85
|
+
nameLight(name: string, target: Target): {
|
|
86
|
+
ok: true;
|
|
87
|
+
name: string;
|
|
88
|
+
vaddr: string;
|
|
89
|
+
};
|
|
90
|
+
forgetName(name: string): {
|
|
91
|
+
ok: true;
|
|
92
|
+
removed: string;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level controller shared by the CLI, the HTTP API and the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* - connects lazily, serialises operations and reconnects once if the link dropped
|
|
5
|
+
* - disconnects after `idleTimeout` seconds without commands, so the phone app can use the mesh again
|
|
6
|
+
* (0 = keep the link open)
|
|
7
|
+
* - accepts light names from the config file ("living"), "all", or a raw vaddr ("00000000")
|
|
8
|
+
*/
|
|
9
|
+
import { InputError, LoginError } from './errors.js';
|
|
10
|
+
import { log } from './log.js';
|
|
11
|
+
import * as P from './protocol.js';
|
|
12
|
+
import { OdelicMesh } from './client.js';
|
|
13
|
+
import { Config } from './config.js';
|
|
14
|
+
/** Integer from a number or numeric string (like Python's int()); throws InputError otherwise. */
|
|
15
|
+
export function toInt(v, what = 'value') {
|
|
16
|
+
const n = typeof v === 'string' && v.trim() !== '' ? Number(v) : v;
|
|
17
|
+
if (typeof n !== 'number' || !Number.isFinite(n))
|
|
18
|
+
throw new InputError(`${what} must be a number (got ${JSON.stringify(v)})`);
|
|
19
|
+
return Math.trunc(n);
|
|
20
|
+
}
|
|
21
|
+
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
22
|
+
const byVaddr = (a, b) => Buffer.compare(a.vaddr, b.vaddr);
|
|
23
|
+
export class Controller {
|
|
24
|
+
config;
|
|
25
|
+
mesh;
|
|
26
|
+
/** seconds; 0 = keep the link open */
|
|
27
|
+
idleTimeout;
|
|
28
|
+
queue = Promise.resolve();
|
|
29
|
+
idle = null;
|
|
30
|
+
/** @param appId default: $ODELIC_ID / config file */
|
|
31
|
+
constructor(appId = null, { node = null, config = null, idleTimeout = null, mesh = null } = {}) {
|
|
32
|
+
this.config = config ?? Config.load();
|
|
33
|
+
const aid = this.config.appId(appId);
|
|
34
|
+
if (!aid && !mesh)
|
|
35
|
+
throw new InputError('no app ID: pass --id, set ODELIC_ID, or run `odelic setup --id <ID>`');
|
|
36
|
+
this.mesh = mesh ?? new OdelicMesh(aid, { device: node ?? this.config.node });
|
|
37
|
+
this.idleTimeout = this.config.idle(idleTimeout);
|
|
38
|
+
}
|
|
39
|
+
// ------------------------------------------------------------ connection handling
|
|
40
|
+
/** Run `fn` after every previously queued operation (a simple mutex). */
|
|
41
|
+
locked(fn) {
|
|
42
|
+
const run = this.queue.then(fn);
|
|
43
|
+
this.queue = run.catch(() => { });
|
|
44
|
+
return run;
|
|
45
|
+
}
|
|
46
|
+
run(fn) {
|
|
47
|
+
return this.locked(async () => {
|
|
48
|
+
this.cancelIdle();
|
|
49
|
+
try {
|
|
50
|
+
for (let attempt = 0;; attempt++) {
|
|
51
|
+
if (!this.mesh.isConnected) {
|
|
52
|
+
log.info(`connecting via ${this.mesh.device ?? 'strongest node'}`);
|
|
53
|
+
await this.mesh.connect();
|
|
54
|
+
}
|
|
55
|
+
else if (this.mesh.suspect) {
|
|
56
|
+
log.info('session looks stale, resynchronising');
|
|
57
|
+
try {
|
|
58
|
+
await this.mesh.resync();
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
log.warning(`resync failed (${e.message}), reconnecting`);
|
|
62
|
+
await this.mesh.disconnect();
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let res;
|
|
67
|
+
try {
|
|
68
|
+
res = await fn(this.mesh);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
// input / login errors are final; anything else may be a dropped link
|
|
72
|
+
if (attempt || e instanceof InputError || e instanceof LoginError)
|
|
73
|
+
throw e;
|
|
74
|
+
log.warning(`request failed (${e.message}), reconnecting`);
|
|
75
|
+
await this.mesh.disconnect();
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// status() resolves to null on timeout: the session may have
|
|
79
|
+
// switched under us (another controller logged in), so make sure
|
|
80
|
+
// the link is live and retry once
|
|
81
|
+
if (res === null && !attempt) {
|
|
82
|
+
log.info('no reply, ensuring the session is live and retrying');
|
|
83
|
+
try {
|
|
84
|
+
await this.mesh.ensureLive();
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
log.warning(`ensureLive failed (${e.message})`);
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
return res;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
this.armIdle();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
cancelIdle() {
|
|
100
|
+
if (this.idle)
|
|
101
|
+
clearTimeout(this.idle);
|
|
102
|
+
this.idle = null;
|
|
103
|
+
}
|
|
104
|
+
armIdle() {
|
|
105
|
+
if (!this.idleTimeout || !this.mesh.isConnected)
|
|
106
|
+
return;
|
|
107
|
+
this.idle = setTimeout(() => {
|
|
108
|
+
this.locked(async () => {
|
|
109
|
+
this.idle = null;
|
|
110
|
+
if (this.mesh.isConnected) {
|
|
111
|
+
log.info('idle, disconnecting');
|
|
112
|
+
await this.mesh.disconnect();
|
|
113
|
+
}
|
|
114
|
+
}).catch((e) => log.warning(`idle disconnect failed: ${e.message}`));
|
|
115
|
+
}, this.idleTimeout * 1000);
|
|
116
|
+
this.idle.unref();
|
|
117
|
+
}
|
|
118
|
+
/** Connect now (otherwise the first command connects). */
|
|
119
|
+
async connect() {
|
|
120
|
+
await this.run(async () => { });
|
|
121
|
+
}
|
|
122
|
+
async close() {
|
|
123
|
+
this.cancelIdle();
|
|
124
|
+
await this.locked(() => this.mesh.disconnect());
|
|
125
|
+
}
|
|
126
|
+
get connected() {
|
|
127
|
+
return this.mesh.isConnected;
|
|
128
|
+
}
|
|
129
|
+
get keepConnected() {
|
|
130
|
+
return !this.idleTimeout;
|
|
131
|
+
}
|
|
132
|
+
info() {
|
|
133
|
+
return { connected: this.connected, node: this.mesh.nodeName, mode: this.keepConnected ? 'keep' : 'idle', idle_timeout: this.idleTimeout };
|
|
134
|
+
}
|
|
135
|
+
// ------------------------------------------------------------ helpers
|
|
136
|
+
label(v) {
|
|
137
|
+
return v.equals(P.BROADCAST) ? 'all' : (this.config.nameOf(v) ?? v.toString('hex'));
|
|
138
|
+
}
|
|
139
|
+
done(action, v, extra = {}) {
|
|
140
|
+
return { ok: true, action, target: this.label(v), ...extra };
|
|
141
|
+
}
|
|
142
|
+
// ------------------------------------------------------------ actions
|
|
143
|
+
async on(target = 'all') {
|
|
144
|
+
const v = this.config.resolve(target);
|
|
145
|
+
await this.run((m) => m.on(v));
|
|
146
|
+
return this.done('on', v);
|
|
147
|
+
}
|
|
148
|
+
async off(target = 'all') {
|
|
149
|
+
const v = this.config.resolve(target);
|
|
150
|
+
await this.run((m) => m.off(v));
|
|
151
|
+
return this.done('off', v);
|
|
152
|
+
}
|
|
153
|
+
/** color 0 (warm) .. 100 (cool), brightness 1..100 % */
|
|
154
|
+
async setLight(target = 'all', color = 50, brightness = 100) {
|
|
155
|
+
const v = this.config.resolve(target);
|
|
156
|
+
const c = clamp(toInt(color, 'color'), 0, 100);
|
|
157
|
+
const b = clamp(toInt(brightness, 'brightness'), 1, 100);
|
|
158
|
+
await this.run((m) => m.setLight({ color: c, brightness: b, target: v }));
|
|
159
|
+
return this.done('light', v, { color: c, brightness: b });
|
|
160
|
+
}
|
|
161
|
+
async nightLight(target = 'all', level = 0) {
|
|
162
|
+
const v = this.config.resolve(target);
|
|
163
|
+
const l = clamp(toInt(level, 'level'), 0, 2);
|
|
164
|
+
await this.run((m) => m.nightLight(l, v));
|
|
165
|
+
return this.done('night', v, { level: l });
|
|
166
|
+
}
|
|
167
|
+
/** action: "on", "off" or "light" (uses color / brightness) */
|
|
168
|
+
async group(groupId, action = 'on', color = 50, brightness = 100) {
|
|
169
|
+
const id = toInt(groupId, 'group id');
|
|
170
|
+
if (action === 'light') {
|
|
171
|
+
const c = clamp(toInt(color, 'color'), 0, 100);
|
|
172
|
+
const b = clamp(toInt(brightness, 'brightness'), 1, 100);
|
|
173
|
+
await this.run((m) => m.setGroup(id, { color: c, brightness: b }));
|
|
174
|
+
}
|
|
175
|
+
else if (action === 'on' || action === 'off') {
|
|
176
|
+
await this.run((m) => m.setGroup(id, { on: action === 'on' }));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
throw new InputError('action must be on, off or light');
|
|
180
|
+
}
|
|
181
|
+
return { ok: true, action: `group_${action}`, group: id };
|
|
182
|
+
}
|
|
183
|
+
async scene(index, save = false) {
|
|
184
|
+
const i = toInt(index, 'scene index');
|
|
185
|
+
await this.run((m) => (save ? m.saveScene(i) : m.playScene(i)));
|
|
186
|
+
return { ok: true, action: save ? 'scene_save' : 'scene_play', index: i };
|
|
187
|
+
}
|
|
188
|
+
async changeColor(target) {
|
|
189
|
+
const v = this.config.resolve(target);
|
|
190
|
+
await this.run((m) => m.changeColor(v));
|
|
191
|
+
return this.done('change_color', v);
|
|
192
|
+
}
|
|
193
|
+
/** Lights answering a mesh ping, plus named lights that did not answer (online: false). */
|
|
194
|
+
async lights(timeout = 3) {
|
|
195
|
+
const devs = await this.run((m) => m.devices(timeout));
|
|
196
|
+
const out = [];
|
|
197
|
+
const seen = new Set();
|
|
198
|
+
for (const d of [...devs].sort(byVaddr)) {
|
|
199
|
+
const hex = d.vaddr.toString('hex');
|
|
200
|
+
seen.add(hex);
|
|
201
|
+
out.push({ name: this.config.nameOf(d.vaddr), vaddr: hex, online: true, mac: d.mac, product: d.product, version: d.version });
|
|
202
|
+
}
|
|
203
|
+
for (const [name, v] of Object.entries(this.config.lights)) {
|
|
204
|
+
if (!seen.has(v))
|
|
205
|
+
out.push({ name, vaddr: v, online: false });
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
/** State of one light, or of every responding light for "all". */
|
|
210
|
+
async status(target = 'all') {
|
|
211
|
+
const v = this.config.resolve(target);
|
|
212
|
+
if (v.equals(P.BROADCAST)) {
|
|
213
|
+
const devs = await this.run((m) => m.devices());
|
|
214
|
+
const out = [];
|
|
215
|
+
for (const d of [...devs].sort(byVaddr))
|
|
216
|
+
out.push(await this.statusOf(d.vaddr));
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
return this.statusOf(v);
|
|
220
|
+
}
|
|
221
|
+
async statusOf(v) {
|
|
222
|
+
const st = await this.run((m) => m.status(v));
|
|
223
|
+
if (st === null)
|
|
224
|
+
return { name: this.config.nameOf(v), vaddr: v.toString('hex'), online: false };
|
|
225
|
+
return { name: this.config.nameOf(v), online: true, ...st.toJSON() };
|
|
226
|
+
}
|
|
227
|
+
// ------------------------------------------------------------ names (no BLE needed)
|
|
228
|
+
nameLight(name, target) {
|
|
229
|
+
this.config.setName(name, target);
|
|
230
|
+
this.config.save();
|
|
231
|
+
return { ok: true, name, vaddr: this.config.lights[name] };
|
|
232
|
+
}
|
|
233
|
+
forgetName(name) {
|
|
234
|
+
this.config.removeName(name);
|
|
235
|
+
this.config.save();
|
|
236
|
+
return { ok: true, removed: name };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** AES-128-ECB without padding; `data` must be a multiple of 16 bytes. */
|
|
2
|
+
export declare function aes128(key: Uint8Array, data: Uint8Array, { decrypt }?: {
|
|
3
|
+
decrypt?: boolean;
|
|
4
|
+
}): Buffer;
|
|
5
|
+
export declare function pkcs7Pad(data: Uint8Array): Buffer;
|
|
6
|
+
export declare function pkcs7Unpad(data: Buffer): Buffer;
|
|
7
|
+
/** Keys for one mesh (HomeID + password) and the per-connection session value. */
|
|
8
|
+
export declare class MeshCrypto {
|
|
9
|
+
readonly homeid: Buffer;
|
|
10
|
+
readonly password: Buffer;
|
|
11
|
+
/** Per-connection value from the login challenge; null until logged in. */
|
|
12
|
+
session: Buffer | null;
|
|
13
|
+
private readonly loginKey;
|
|
14
|
+
private readonly eventKey;
|
|
15
|
+
/** @param password 4 ASCII characters */
|
|
16
|
+
constructor(homeid: number, password: string);
|
|
17
|
+
/** `challenge` = the 16 encrypted bytes following `02 20`. Returns the login packet. */
|
|
18
|
+
answerChallenge(challenge: Uint8Array): Buffer;
|
|
19
|
+
/**
|
|
20
|
+
* Follow a login challenge we did not ask for.
|
|
21
|
+
*
|
|
22
|
+
* The node switches to the session value of every new challenge, even when that login is
|
|
23
|
+
* later refused with status 08. On macOS all processes share one BLE link per peripheral,
|
|
24
|
+
* so another program logging in through the same link silently invalidates our session
|
|
25
|
+
* unless we switch too. Returns true if the session changed.
|
|
26
|
+
*/
|
|
27
|
+
adoptChallenge(challenge: Uint8Array): boolean;
|
|
28
|
+
private xor;
|
|
29
|
+
/** `03 <dst4> <ch> <payload>` -> `06 <dst4> <ch> <cipher>` */
|
|
30
|
+
encrypt(packet: Uint8Array): Buffer;
|
|
31
|
+
/** `06 <src/dst4> <ch> <cipher>` -> `03 <...> <payload>`; other packet types pass through. */
|
|
32
|
+
decrypt(data: Uint8Array): Buffer;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAiBA,0EAA0E;AAC1E,wBAAgB,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,OAAe,EAAE,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAIjH;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAGjD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,kFAAkF;AAClF,qBAAa,UAAU;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,yCAAyC;IACzC,YAAY,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAiB3C;IAED,wFAAwF;IACxF,eAAe,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAQ7C;IAED;;;;;;;OAOG;IACH,cAAc,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAO7C;IAED,OAAO,CAAC,GAAG;IAIX,gEAAgE;IAChE,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAMlC;IAED,gGAAgG;IAChG,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAOhC;CACF"}
|