odelic 0.0.0-stage → 0.2.1

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -2
  3. package/bin/odelic-mcp.js +5 -0
  4. package/bin/odelic.js +6 -0
  5. package/dist/cli.d.ts +58 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +279 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/client.d.ts +125 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +354 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/config.d.ts +34 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +139 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/controller.d.ts +95 -0
  18. package/dist/controller.d.ts.map +1 -0
  19. package/dist/controller.js +210 -0
  20. package/dist/controller.js.map +1 -0
  21. package/dist/crypto.d.ts +25 -0
  22. package/dist/crypto.d.ts.map +1 -0
  23. package/dist/crypto.js +94 -0
  24. package/dist/crypto.js.map +1 -0
  25. package/dist/errors.d.ts +14 -0
  26. package/dist/errors.d.ts.map +1 -0
  27. package/dist/errors.js +17 -0
  28. package/dist/errors.js.map +1 -0
  29. package/dist/index.d.ts +18 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +12 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/log.d.ts +18 -0
  34. package/dist/log.d.ts.map +1 -0
  35. package/dist/log.js +21 -0
  36. package/dist/log.js.map +1 -0
  37. package/dist/mcp.d.ts +23 -0
  38. package/dist/mcp.d.ts.map +1 -0
  39. package/dist/mcp.js +74 -0
  40. package/dist/mcp.js.map +1 -0
  41. package/dist/protocol.d.ts +90 -0
  42. package/dist/protocol.d.ts.map +1 -0
  43. package/dist/protocol.js +178 -0
  44. package/dist/protocol.js.map +1 -0
  45. package/dist/server.d.ts +24 -0
  46. package/dist/server.d.ts.map +1 -0
  47. package/dist/server.js +187 -0
  48. package/dist/server.js.map +1 -0
  49. package/dist/version.d.ts +2 -0
  50. package/dist/version.d.ts.map +1 -0
  51. package/dist/version.js +2 -0
  52. package/dist/version.js.map +1 -0
  53. package/package.json +80 -4
  54. package/src/cli.ts +267 -0
  55. package/src/client.ts +414 -0
  56. package/src/config.ts +142 -0
  57. package/src/controller.ts +267 -0
  58. package/src/crypto.ts +100 -0
  59. package/src/errors.ts +17 -0
  60. package/src/index.ts +17 -0
  61. package/src/log.ts +24 -0
  62. package/src/mcp.ts +100 -0
  63. package/src/protocol.ts +205 -0
  64. package/src/server.ts +195 -0
  65. package/src/version.ts +1 -0
package/dist/errors.js ADDED
@@ -0,0 +1,17 @@
1
+ /** Error types. `InputError` = bad user input (HTTP 400); the others come from the mesh / BLE side. */
2
+ export class OdelicError extends Error {
3
+ constructor(message, options) {
4
+ super(message, options);
5
+ this.name = new.target.name;
6
+ }
7
+ }
8
+ /** Invalid argument: unknown light name, malformed vaddr / ID, out-of-range number, ... */
9
+ export class InputError extends OdelicError {
10
+ }
11
+ /** The node rejected the login (wrong HomeID / password, or another controller is connected). */
12
+ export class LoginError extends OdelicError {
13
+ }
14
+ /** No mesh node found, or the BLE link could not be established. */
15
+ export class ConnectionError extends OdelicError {
16
+ }
17
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAEvG,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAgB,EAAE,OAAsB;QAClD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED,2FAA2F;AAC3F,MAAM,OAAO,UAAW,SAAQ,WAAW;CAAG;AAE9C,iGAAiG;AACjG,MAAM,OAAO,UAAW,SAAQ,WAAW;CAAG;AAE9C,oEAAoE;AACpE,MAAM,OAAO,eAAgB,SAAQ,WAAW;CAAG"}
@@ -0,0 +1,18 @@
1
+ /** Unofficial Node.js library for ODELIC Bluetooth lights (Pairlink connected-mesh). */
2
+ export { OdelicMesh, findNodes } from './client.ts';
3
+ export type { MeshNode, OdelicMeshOptions, PacketListener, SetGroupOptions, SetLightOptions } from './client.ts';
4
+ export { Config, configPath, parseIdle } from './config.ts';
5
+ export type { ConfigData } from './config.ts';
6
+ export { Controller } from './controller.ts';
7
+ export type { ActionResult, ControllerInfo, ControllerOptions, LightInfo, LightStatus, Target } from './controller.ts';
8
+ export { MeshCrypto } from './crypto.ts';
9
+ export { ConnectionError, InputError, LoginError, OdelicError } from './errors.ts';
10
+ export { setLogLevel } from './log.ts';
11
+ export type { LogLevel } from './log.ts';
12
+ export { BROADCAST, Device, LightState, parseAppId } from './protocol.ts';
13
+ export type { LightStateJSON, VaddrLike } from './protocol.ts';
14
+ export * as protocol from './protocol.ts';
15
+ export { serve } from './server.ts';
16
+ export type { ServeOptions } from './server.ts';
17
+ export { VERSION } from './version.ts';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACjH,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACvH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1E,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /** Unofficial Node.js library for ODELIC Bluetooth lights (Pairlink connected-mesh). */
2
+ export { OdelicMesh, findNodes } from './client.js';
3
+ export { Config, configPath, parseIdle } from './config.js';
4
+ export { Controller } from './controller.js';
5
+ export { MeshCrypto } from './crypto.js';
6
+ export { ConnectionError, InputError, LoginError, OdelicError } from './errors.js';
7
+ export { setLogLevel } from './log.js';
8
+ export { BROADCAST, Device, LightState, parseAppId } from './protocol.js';
9
+ export * as protocol from './protocol.js';
10
+ export { serve } from './server.js';
11
+ export { VERSION } from './version.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE1E,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/dist/log.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /** Minimal leveled logger writing to stderr (stdout stays clean for the CLI output and MCP stdio). */
2
+ declare const LEVELS: {
3
+ readonly debug: 10;
4
+ readonly info: 20;
5
+ readonly warning: 30;
6
+ readonly error: 40;
7
+ readonly silent: 100;
8
+ };
9
+ export type LogLevel = keyof typeof LEVELS;
10
+ export declare function setLogLevel(level: LogLevel): void;
11
+ export declare const log: {
12
+ debug: (...a: unknown[]) => void;
13
+ info: (...a: unknown[]) => void;
14
+ warning: (...a: unknown[]) => void;
15
+ error: (...a: unknown[]) => void;
16
+ };
17
+ export {};
18
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAEtG,QAAA,MAAM,MAAM;aAAK,KAAK,EAAE,EAAE;aAAE,IAAI,EAAE,EAAE;aAAE,OAAO,EAAE,EAAE;aAAE,KAAK,EAAE,EAAE;aAAE,MAAM,EAAE,GAAG;CAAW,CAAC;AACrF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,MAAM,CAAC;AAM3C,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAGjD;AAMD,eAAO,MAAM,GAAG;IACd,KAAK,SAAS,OAAO,EAAE;IACvB,IAAI,SAAS,OAAO,EAAE;IACtB,OAAO,SAAS,OAAO,EAAE;IACzB,KAAK,SAAS,OAAO,EAAE;CACxB,CAAC"}
package/dist/log.js ADDED
@@ -0,0 +1,21 @@
1
+ /** Minimal leveled logger writing to stderr (stdout stays clean for the CLI output and MCP stdio). */
2
+ const LEVELS = { debug: 10, info: 20, warning: 30, error: 40, silent: 100 };
3
+ const isLevel = (v) => Object.hasOwn(LEVELS, v);
4
+ const fromEnv = String(process.env.ODELIC_LOG ?? '').toLowerCase();
5
+ let current = isLevel(fromEnv) ? LEVELS[fromEnv] : LEVELS.warning;
6
+ export function setLogLevel(level) {
7
+ if (!isLevel(level))
8
+ throw new RangeError(`unknown log level ${level}`);
9
+ current = LEVELS[level];
10
+ }
11
+ function emit(level, args) {
12
+ if (LEVELS[level] >= current)
13
+ console.error('odelic:', ...args);
14
+ }
15
+ export const log = {
16
+ debug: (...a) => emit('debug', a),
17
+ info: (...a) => emit('info', a),
18
+ warning: (...a) => emit('warning', a),
19
+ error: (...a) => emit('error', a),
20
+ };
21
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAEtG,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAW,CAAC;AAGrF,MAAM,OAAO,GAAG,CAAC,CAAS,EAAiB,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACvE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACnE,IAAI,OAAO,GAAW,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;AAE1E,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,qBAAqB,KAAK,EAAE,CAAC,CAAC;IACxE,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,IAAI,CAAC,KAAe,EAAE,IAAe;IAC5C,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO;QAAE,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,KAAK,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5C,IAAI,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,OAAO,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;CAC7C,CAAC"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * MCP server exposing ODELIC lights to AI assistants (stdio).
3
+ *
4
+ * npm install odelic @modelcontextprotocol/sdk zod
5
+ * odelic-mcp # reads the ID from ODELIC_ID or the config file (`odelic setup --id ...`)
6
+ *
7
+ * The BLE link is opened on the first command. By default it is released after 60 s without
8
+ * commands so the official phone app can still connect; set ODELIC_IDLE_TIMEOUT (seconds, 0 = stay
9
+ * connected) or `odelic setup --keep-connected` to change that.
10
+ */
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { Controller } from './controller.ts';
13
+ export declare const INSTRUCTIONS = "Controls ODELIC Bluetooth ceiling/room lights in the user's home.\nTargets: a light name (see list_lights), or \"all\" for every light.\nColour is 0 = warm/\u96FB\u7403\u8272 (orange) ... 100 = cool/\u663C\u5149\u8272 (bluish white); brightness is 1..100 %.\nCommands are fire-and-forget; use light_status to confirm the result if needed.";
14
+ /** The part of Controller the MCP tools use. */
15
+ export type McpController = Pick<Controller, 'lights' | 'status' | 'on' | 'off' | 'setLight' | 'nightLight' | 'group' | 'scene' | 'nameLight' | 'forgetName'>;
16
+ /**
17
+ * Build the MCP server.
18
+ * @param getController called lazily on the first tool call
19
+ */
20
+ export declare function createMcpServer(getController?: () => McpController): McpServer;
21
+ /** Run the MCP server on stdio (logs go to stderr). */
22
+ export declare function main(): Promise<void>;
23
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,eAAO,MAAM,YAAY,uVAGuD,CAAC;AAEjF,gDAAgD;AAChD,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EACzC,QAAQ,GAAG,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;AAanH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,aAAa,GAAE,MAAM,aAAgC,GAAG,SAAS,CA6ChG;AAQD,uDAAuD;AACvD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1C"}
package/dist/mcp.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * MCP server exposing ODELIC lights to AI assistants (stdio).
3
+ *
4
+ * npm install odelic @modelcontextprotocol/sdk zod
5
+ * odelic-mcp # reads the ID from ODELIC_ID or the config file (`odelic setup --id ...`)
6
+ *
7
+ * The BLE link is opened on the first command. By default it is released after 60 s without
8
+ * commands so the official phone app can still connect; set ODELIC_IDLE_TIMEOUT (seconds, 0 = stay
9
+ * connected) or `odelic setup --keep-connected` to change that.
10
+ */
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
+ import { z } from 'zod';
14
+ import { Controller } from './controller.js';
15
+ import { VERSION } from './version.js';
16
+ export const INSTRUCTIONS = `Controls ODELIC Bluetooth ceiling/room lights in the user's home.
17
+ Targets: a light name (see list_lights), or "all" for every light.
18
+ Colour is 0 = warm/電球色 (orange) ... 100 = cool/昼光色 (bluish white); brightness is 1..100 %.
19
+ Commands are fire-and-forget; use light_status to confirm the result if needed.`;
20
+ const READ_ONLY = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };
21
+ const ACTION = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
22
+ const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };
23
+ const target = z.string().default('all').describe('light name, vaddr, or "all"');
24
+ const color = z.number().int().min(0).max(100).default(50).describe('0 = warm/電球色 .. 50 = neutral .. 100 = cool/昼光色');
25
+ const brightness = z.number().int().min(1).max(100).default(100).describe('1..100 (%)');
26
+ const byte = z.number().int().min(0).max(255);
27
+ const result = (value) => ({ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] });
28
+ /**
29
+ * Build the MCP server.
30
+ * @param getController called lazily on the first tool call
31
+ */
32
+ export function createMcpServer(getController = lazyController()) {
33
+ const server = new McpServer({ name: 'odelic', version: VERSION }, { instructions: INSTRUCTIONS });
34
+ const ctl = () => getController();
35
+ function tool(name, description, annotations, inputSchema, fn) {
36
+ const handler = async (args) => {
37
+ try {
38
+ return result(await fn(args));
39
+ }
40
+ catch (e) {
41
+ const err = e;
42
+ return { ...result({ ok: false, error: `${err.name}: ${err.message}` }), isError: true };
43
+ }
44
+ };
45
+ // the SDK's registerTool generics do not narrow well through this helper
46
+ server.registerTool(name, { description, annotations, inputSchema }, handler);
47
+ }
48
+ tool('list_lights', 'List the lights in the mesh (name, vaddr, online, product). Takes a few seconds.', READ_ONLY, {}, () => ctl().lights());
49
+ tool('light_status', 'Current state of a light: on/off, colour (0 warm..100 cool) and brightness (%).', READ_ONLY, { target }, async (a) => {
50
+ const res = await ctl().status(a.target);
51
+ return Array.isArray(res) ? { lights: res } : res;
52
+ });
53
+ tool('turn_on', 'Turn a light (or "all") on with its previous colour and brightness.', ACTION, { target }, (a) => ctl().on(a.target));
54
+ tool('turn_off', 'Turn a light (or "all") off.', ACTION, { target }, (a) => ctl().off(a.target));
55
+ tool('set_light', 'Turn on with a given colour and brightness.', ACTION, { target, color, brightness }, (a) => ctl().setLight(a.target, a.color, a.brightness));
56
+ tool('night_light', 'Very dim warm night light. level 0..2.', ACTION, { target, level: z.number().int().min(0).max(2).default(0) }, (a) => ctl().nightLight(a.target, a.level));
57
+ tool('control_group', 'Control a group configured in the ODELIC app. action: "on", "off" or "light" (uses color/brightness).', ACTION, { group_id: byte, action: z.enum(['on', 'off', 'light']).default('on'), color, brightness }, (a) => ctl().group(a.group_id, a.action, a.color, a.brightness));
58
+ tool('play_scene', 'Recall a scene saved in the lights (index as in the app).', ACTION, { index: byte }, (a) => ctl().scene(a.index, false));
59
+ tool('save_scene', 'Store the current state of all lights as scene `index` (overwrites that scene).', DESTRUCTIVE, { index: byte }, (a) => ctl().scene(a.index, true));
60
+ tool('name_light', 'Give a light (vaddr from list_lights) a friendly name such as "living" or "リビング".', { readOnlyHint: false, destructiveHint: false, openWorldHint: false }, { name: z.string().min(1), vaddr: z.string() }, (a) => ctl().nameLight(a.name, a.vaddr));
61
+ tool('forget_light_name', 'Remove a light name from the config.', DESTRUCTIVE, { name: z.string() }, (a) => ctl().forgetName(a.name));
62
+ return server;
63
+ }
64
+ function lazyController() {
65
+ let c = null;
66
+ // idle timeout from $ODELIC_IDLE_TIMEOUT / config (0 = stay connected)
67
+ return () => (c ??= new Controller());
68
+ }
69
+ /** Run the MCP server on stdio (logs go to stderr). */
70
+ export async function main() {
71
+ const server = createMcpServer(lazyController());
72
+ await server.connect(new StdioServerTransport());
73
+ }
74
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,CAAC,MAAM,YAAY,GAAG;;;gFAGoD,CAAC;AAMjF,MAAM,SAAS,GAAoB,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACtG,MAAM,MAAM,GAAoB,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AAC5H,MAAM,WAAW,GAAoB,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AAE1G,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,6BAA6B,CAAC,CAAC;AACjF,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,gDAAgD,CAAC,CAAC;AACtH,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AACxF,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE9C,MAAM,MAAM,GAAG,CAAC,KAAc,EAAkB,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAE3H;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,aAAa,GAAwB,cAAc,EAAE;IACnF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC,CAAC;IACnG,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC;IAElC,SAAS,IAAI,CAA0B,IAAY,EAAE,WAAmB,EAAE,WAA4B,EAAE,WAAc,EACpH,EAA8C;QAC9C,MAAM,OAAO,GAAG,KAAK,EAAE,IAA6B,EAA2B,EAAE;YAC/E,IAAI,CAAC;gBACH,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,GAAG,GAAG,CAAU,CAAC;gBACvB,OAAO,EAAE,GAAG,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC3F,CAAC;QACH,CAAC,CAAC;QACF,yEAAyE;QACxE,MAAM,CAAC,YAAqE,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC;IAC1I,CAAC;IAED,IAAI,CAAC,aAAa,EAAE,kFAAkF,EACpG,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IACvC,IAAI,CAAC,cAAc,EAAE,iFAAiF,EACpG,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IACpD,CAAC,CAAC,CAAC;IACL,IAAI,CAAC,SAAS,EAAE,qEAAqE,EACnF,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,IAAI,CAAC,UAAU,EAAE,8BAA8B,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACjG,IAAI,CAAC,WAAW,EAAE,6CAA6C,EAC7D,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACjG,IAAI,CAAC,aAAa,EAAE,wCAAwC,EAC1D,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACpH,IAAI,CAAC,eAAe,EAAE,uGAAuG,EAC3H,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EACnG,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,YAAY,EAAE,2DAA2D,EAC5E,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/D,IAAI,CAAC,YAAY,EAAE,iFAAiF,EAClG,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,YAAY,EAAE,mFAAmF,EACpG,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,EACrH,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,IAAI,CAAC,mBAAmB,EAAE,sCAAsC,EAC9D,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,CAAC,GAAsB,IAAI,CAAC;IAChC,uEAAuE;IACvE,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,UAAU,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,MAAM,GAAG,eAAe,CAAC,cAAc,EAAE,CAAC,CAAC;IACjD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,90 @@
1
+ /** A light address: 4 bytes, a number (little endian) or a hex string ("00000000", "00:00:00:00"). */
2
+ export type VaddrLike = Uint8Array | number | string;
3
+ export declare const BROADCAST: Buffer;
4
+ export declare const CH_TO_LIGHT = 32;
5
+ export declare const CH_TO_LIGHT_BROADCAST = 42;
6
+ export declare const CH_PING = 254;
7
+ export declare const CH_PING_RESPONSE = 255;
8
+ export declare const MSG_LIGHT = 192;
9
+ export declare const MSG_LIGHT_GROUP = 193;
10
+ export declare const MSG_CHANGE_COLOR = 226;
11
+ export declare const MSG_SCENE_SAVE = 64;
12
+ export declare const MSG_SCENE_PLAY = 66;
13
+ export declare const MSG_STATUS = 112;
14
+ export declare const MSG_STATUS_RESPONSES: readonly number[];
15
+ export declare const XY_ON: readonly [55, 55];
16
+ export declare const XY_OFF: readonly [50, 50];
17
+ export declare const X_MAX = 20;
18
+ export declare const Y_MAX = 19;
19
+ /** 0 (warmest) .. 100 (coolest) -> x 0..20 */
20
+ export declare function colorToX(color: number): number;
21
+ /** 1..100 % -> y 19..0 (same rule as the app: (100 - b) / 5, clamped) */
22
+ export declare function brightnessToY(brightness: number): number;
23
+ /** 4-byte vaddr from a Buffer/Uint8Array, a number (little endian) or a hex string. */
24
+ export declare function vaddr(v: VaddrLike): Buffer;
25
+ export declare function dataEvent(dst: Uint8Array, channel: number, payload: Uint8Array): Buffer;
26
+ /** ControllerAct.setlight: on/off/colour/brightness for one light or all (broadcast). */
27
+ export declare function light(own: Uint8Array, dst: Buffer, x: number, y: number, index?: number): Buffer;
28
+ /** ControllerAct.sendgroup_new */
29
+ export declare function groupLight(own: Uint8Array, groupId: number, x: number, y: number): Buffer;
30
+ export declare function changeColor(own: Uint8Array, dst: Uint8Array): Buffer;
31
+ export declare function scene(own: Uint8Array, index: number, save: boolean, dst?: Uint8Array): Buffer;
32
+ export declare function statusRequest(own: Uint8Array, dst: Uint8Array): Buffer;
33
+ export declare function pingAll(own: Uint8Array): Buffer;
34
+ /** A light that answered a mesh ping. */
35
+ export declare class Device {
36
+ readonly vaddr: Buffer;
37
+ readonly mac: string;
38
+ readonly product: number;
39
+ readonly version: string;
40
+ constructor({ vaddr, mac, product, version }: {
41
+ vaddr: Buffer;
42
+ mac: string;
43
+ product: number;
44
+ version: string;
45
+ });
46
+ toString(): string;
47
+ }
48
+ /** MeshCommon.GetPingResponse; `p` is a decrypted type-3 packet with channel 0xFF. */
49
+ export declare function parsePingResponse(p: Buffer): Device | null;
50
+ export interface LightStateJSON {
51
+ vaddr: string;
52
+ on: boolean;
53
+ /** 0 = warm .. 100 = cool, null if unknown */
54
+ color: number | null;
55
+ /** 1..100 %, 0 when off, null if unknown */
56
+ brightness: number | null;
57
+ x: number;
58
+ y: number;
59
+ }
60
+ /** State reported by one light. */
61
+ export declare class LightState {
62
+ readonly vaddr: Buffer;
63
+ readonly on: boolean;
64
+ readonly x: number;
65
+ readonly y: number;
66
+ constructor({ vaddr, on, x, y }: {
67
+ vaddr: Buffer;
68
+ on: boolean;
69
+ x: number;
70
+ y: number;
71
+ });
72
+ /** 0 = warm .. 100 = cool, null if unknown (light reports a plain "on"). */
73
+ get color(): number | null;
74
+ /** 1..100 %, 0 when off, null if unknown. */
75
+ get brightness(): number | null;
76
+ toJSON(): LightStateJSON;
77
+ }
78
+ /** Status reply (MeshBlePresenter.syncState): `03 <dst4> <ch> <src4> 71 <x> <y> ...` */
79
+ export declare function parseStatus(p: Buffer): LightState | null;
80
+ /** The app shows "ID:<HomeID><4-char password>", e.g. "ID:12345678" -> { homeid: 1234, password: "5678" }. */
81
+ export declare function parseAppId(appId: string): {
82
+ homeid: number;
83
+ password: string;
84
+ };
85
+ /**
86
+ * HomeID advertised by a mesh node (manufacturer id 0xFFFF, c0/c1 ff 01|02 <homeid LE>).
87
+ * `manufacturerData` is the raw AD field as noble reports it, i.e. starting with the 2-byte company id.
88
+ */
89
+ export declare function advHomeid(manufacturerData: Buffer | null | undefined): number | null;
90
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Packet builders/parsers for ODELIC lights on the Pairlink mesh.
3
+ *
4
+ * Mirrors jp.co.odelic.smt.remote03.act.module.ControllerAct in "central" (Tiger) mode:
5
+ * 03 <dst vaddr 4B> <channel> <own vaddr 4B> <msg id> <args...>
6
+ */
7
+ import { InputError } from './errors.js';
8
+ export const BROADCAST = Buffer.from([0xff, 0xff, 0xff, 0xff]);
9
+ // channels
10
+ export const CH_TO_LIGHT = 0x20;
11
+ export const CH_TO_LIGHT_BROADCAST = 0x2a;
12
+ export const CH_PING = 0xfe;
13
+ export const CH_PING_RESPONSE = 0xff;
14
+ // message ids (MeshProfile.DATAEVENT_MSGID_*)
15
+ export const MSG_LIGHT = 0xc0; // SM_BRIGHT_LIGHT
16
+ export const MSG_LIGHT_GROUP = 0xc1; // SM_BRIGHT_LIGHT_GROUP
17
+ export const MSG_CHANGE_COLOR = 0xe2; // SM_CHANGE_COLOR (cycle colour preset)
18
+ export const MSG_SCENE_SAVE = 0x40; // SM_SCENE_IN
19
+ export const MSG_SCENE_PLAY = 0x42; // SM_SCENE_PLAY
20
+ export const MSG_STATUS = 0x70; // SM_STATUS
21
+ export const MSG_STATUS_RESPONSES = [0x71, 0x35]; // STATUS_MAIN / STATE_RESPONSE_FD
22
+ // special (x, y) pairs
23
+ export const XY_ON = [55, 55];
24
+ export const XY_OFF = [50, 50];
25
+ export const X_MAX = 20; // colour: 0 = warm (電球色) ... 20 = cool (昼光色)
26
+ export const Y_MAX = 19; // brightness: 0 = brightest ... 19 = dimmest
27
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
28
+ /** 0 (warmest) .. 100 (coolest) -> x 0..20 */
29
+ export function colorToX(color) {
30
+ return clamp(Math.round((color * X_MAX) / 100), 0, X_MAX);
31
+ }
32
+ /** 1..100 % -> y 19..0 (same rule as the app: (100 - b) / 5, clamped) */
33
+ export function brightnessToY(brightness) {
34
+ return clamp(Math.floor((100 - brightness) / 5), 0, Y_MAX);
35
+ }
36
+ /** 4-byte vaddr from a Buffer/Uint8Array, a number (little endian) or a hex string. */
37
+ export function vaddr(v) {
38
+ let b;
39
+ if (v instanceof Uint8Array) {
40
+ b = Buffer.from(v);
41
+ }
42
+ else if (typeof v === 'number') {
43
+ if (!Number.isInteger(v) || v < 0 || v > 0xffffffff)
44
+ throw new InputError(`vaddr out of range: ${v}`);
45
+ b = Buffer.alloc(4);
46
+ b.writeUInt32LE(v);
47
+ }
48
+ else {
49
+ const s = String(v).replace(/:/g, '');
50
+ if (!/^[0-9a-fA-F]*$/.test(s) || s.length % 2)
51
+ throw new InputError(`invalid vaddr: ${v}`);
52
+ b = Buffer.from(s, 'hex');
53
+ }
54
+ if (b.length !== 4)
55
+ throw new InputError('vaddr must be 4 bytes');
56
+ return b;
57
+ }
58
+ /** Concatenate Buffers and byte arrays; like Python's bytes(), array values must be integers 0..255. */
59
+ function bytes(...parts) {
60
+ return Buffer.concat(parts.map((p) => {
61
+ if (p instanceof Uint8Array)
62
+ return p;
63
+ for (const v of p) {
64
+ if (!Number.isInteger(v) || v < 0 || v > 255)
65
+ throw new InputError(`byte value out of range: ${v}`);
66
+ }
67
+ return Buffer.from(p);
68
+ }));
69
+ }
70
+ export function dataEvent(dst, channel, payload) {
71
+ return bytes([0x03], dst, [channel], payload);
72
+ }
73
+ /** ControllerAct.setlight: on/off/colour/brightness for one light or all (broadcast). */
74
+ export function light(own, dst, x, y, index = 0) {
75
+ const ch = dst.equals(BROADCAST) ? CH_TO_LIGHT_BROADCAST : CH_TO_LIGHT;
76
+ return dataEvent(dst, ch, bytes(own, [MSG_LIGHT, index, x, y, 0, 0, 0, 0]));
77
+ }
78
+ /** ControllerAct.sendgroup_new */
79
+ export function groupLight(own, groupId, x, y) {
80
+ return dataEvent(BROADCAST, CH_TO_LIGHT, bytes(own, [MSG_LIGHT_GROUP, x, y, 0, 0, 0, 0, 0, 0, groupId]));
81
+ }
82
+ export function changeColor(own, dst) {
83
+ return dataEvent(dst, CH_TO_LIGHT, bytes(own, [MSG_CHANGE_COLOR]));
84
+ }
85
+ export function scene(own, index, save, dst = BROADCAST) {
86
+ const msg = save ? MSG_SCENE_SAVE : MSG_SCENE_PLAY;
87
+ return dataEvent(dst, CH_TO_LIGHT, bytes(own, [msg, 0, 0, 0, 0, 0, 0, index]));
88
+ }
89
+ export function statusRequest(own, dst) {
90
+ return dataEvent(dst, CH_TO_LIGHT, bytes(own, [MSG_STATUS]));
91
+ }
92
+ export function pingAll(own) {
93
+ return dataEvent(BROADCAST, CH_PING, own);
94
+ }
95
+ /** A light that answered a mesh ping. */
96
+ export class Device {
97
+ vaddr;
98
+ mac;
99
+ product;
100
+ version;
101
+ constructor({ vaddr, mac, product, version }) {
102
+ this.vaddr = vaddr;
103
+ this.mac = mac;
104
+ this.product = product;
105
+ this.version = version;
106
+ }
107
+ toString() {
108
+ return `vaddr=${this.vaddr.toString('hex')} mac=${this.mac} product=${this.product} ver=${this.version}`;
109
+ }
110
+ }
111
+ /** MeshCommon.GetPingResponse; `p` is a decrypted type-3 packet with channel 0xFF. */
112
+ export function parsePingResponse(p) {
113
+ if (p.length < 20 || p[0] !== 0x03 || p[5] !== CH_PING_RESPONSE)
114
+ return null;
115
+ const mac = [...p.subarray(6, 12)].reverse().map((b) => b.toString(16).toUpperCase().padStart(2, '0')).join(':');
116
+ const product = p[16] | (p[17] << 8);
117
+ return new Device({ vaddr: Buffer.from(p.subarray(12, 16)), mac, product, version: `${product}.${p[18]}.${p[19]}` });
118
+ }
119
+ /** State reported by one light. */
120
+ export class LightState {
121
+ vaddr;
122
+ on;
123
+ x;
124
+ y;
125
+ constructor({ vaddr, on, x, y }) {
126
+ this.vaddr = vaddr;
127
+ this.on = on;
128
+ this.x = x;
129
+ this.y = y;
130
+ }
131
+ /** 0 = warm .. 100 = cool, null if unknown (light reports a plain "on"). */
132
+ get color() {
133
+ return !this.on || this.x > X_MAX ? null : this.x * 5;
134
+ }
135
+ /** 1..100 %, 0 when off, null if unknown. */
136
+ get brightness() {
137
+ if (!this.on)
138
+ return 0;
139
+ return this.y > Y_MAX ? null : 100 - this.y * 5;
140
+ }
141
+ toJSON() {
142
+ return { vaddr: this.vaddr.toString('hex'), on: this.on, color: this.color, brightness: this.brightness, x: this.x, y: this.y };
143
+ }
144
+ }
145
+ /** Status reply (MeshBlePresenter.syncState): `03 <dst4> <ch> <src4> 71 <x> <y> ...` */
146
+ export function parseStatus(p) {
147
+ if (p.length < 13 || p[0] !== 0x03 || !MSG_STATUS_RESPONSES.includes(p[10]))
148
+ return null;
149
+ const x = p[11];
150
+ const y = p[12];
151
+ return new LightState({ vaddr: Buffer.from(p.subarray(6, 10)), on: !(x === XY_OFF[0] && y === XY_OFF[1]), x, y });
152
+ }
153
+ /** The app shows "ID:<HomeID><4-char password>", e.g. "ID:12345678" -> { homeid: 1234, password: "5678" }. */
154
+ export function parseAppId(appId) {
155
+ let s = String(appId).trim();
156
+ if (s.toUpperCase().startsWith('ID:'))
157
+ s = s.slice(3);
158
+ if (s.length < 5)
159
+ throw new InputError(`ID looks too short: ${JSON.stringify(String(appId))}`);
160
+ const head = s.slice(0, -4);
161
+ if (!/^\d+$/.test(head))
162
+ throw new InputError(`ID must start with the numeric HomeID: ${JSON.stringify(String(appId))}`);
163
+ return { homeid: Number(head), password: s.slice(-4) };
164
+ }
165
+ /**
166
+ * HomeID advertised by a mesh node (manufacturer id 0xFFFF, c0/c1 ff 01|02 <homeid LE>).
167
+ * `manufacturerData` is the raw AD field as noble reports it, i.e. starting with the 2-byte company id.
168
+ */
169
+ export function advHomeid(manufacturerData) {
170
+ const m = manufacturerData;
171
+ if (!m || m.length < 9 || m[0] !== 0xff || m[1] !== 0xff)
172
+ return null;
173
+ const v = m.subarray(2);
174
+ if ((v[0] === 0xc0 || v[0] === 0xc1) && v[1] === 0xff && (v[2] === 1 || v[2] === 2))
175
+ return v.readUInt32LE(3);
176
+ return null;
177
+ }
178
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { IncomingMessage, Server, ServerResponse } from 'node:http';
2
+ import type { Controller } from './controller.ts';
3
+ export declare const LOOPBACK: readonly string[];
4
+ /** The part of Controller the HTTP API uses. */
5
+ export type HttpController = Pick<Controller, 'info' | 'lights' | 'status' | 'on' | 'off' | 'setLight' | 'nightLight' | 'group' | 'scene'>;
6
+ type Params = Record<string, unknown>;
7
+ export declare class HttpError extends Error {
8
+ readonly status: number;
9
+ constructor(status: number, message: string);
10
+ }
11
+ /** Route one request to the controller. Returns the JSON-able result. */
12
+ export declare function dispatch(ctl: HttpController, method: string, path: string, params: Params): Promise<unknown>;
13
+ /** A `(req, res)` handler for node:http. */
14
+ export declare function makeHandler(ctl: HttpController, token?: string | null): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
15
+ export interface ServeOptions {
16
+ host?: string;
17
+ port?: number;
18
+ /** bearer token; required when listening on a non-loopback address */
19
+ token?: string | null;
20
+ }
21
+ /** Start the HTTP API. Resolves to the listening server; closing the server also closes the controller. */
22
+ export declare function serve(ctl: Controller, { host, port, token }?: ServeOptions): Promise<Server>;
23
+ export {};
24
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,eAAO,MAAM,QAAQ,EAAE,SAAS,MAAM,EAAsC,CAAC;AAG7E,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,EAC1C,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC;AAE/F,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtC,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,YAAY,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI1C;CACF;AAQD,yEAAyE;AACzE,wBAAsB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkClH;AA2BD,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,SACvD,eAAe,OAAO,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC,CA4CxE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,2GAA2G;AAC3G,wBAAsB,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,IAAkB,EAAE,IAAW,EAAE,KAAY,EAAE,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BlI"}