@martyndevries/xiaomi-miio 0.1.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.
Potentially problematic release.
This version of @martyndevries/xiaomi-miio might be problematic. Click here for more details.
- package/README.md +102 -0
- package/dist/device.d.ts +31 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +39 -0
- package/dist/device.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/crypto.d.ts +19 -0
- package/dist/protocol/crypto.d.ts.map +1 -0
- package/dist/protocol/crypto.js +34 -0
- package/dist/protocol/crypto.js.map +1 -0
- package/dist/protocol/discovery.d.ts +30 -0
- package/dist/protocol/discovery.d.ts.map +1 -0
- package/dist/protocol/discovery.js +73 -0
- package/dist/protocol/discovery.js.map +1 -0
- package/dist/protocol/packet.d.ts +28 -0
- package/dist/protocol/packet.d.ts.map +1 -0
- package/dist/protocol/packet.js +73 -0
- package/dist/protocol/packet.js.map +1 -0
- package/dist/protocol/protocol.d.ts +20 -0
- package/dist/protocol/protocol.d.ts.map +1 -0
- package/dist/protocol/protocol.js +25 -0
- package/dist/protocol/protocol.js.map +1 -0
- package/dist/protocol/transport.d.ts +43 -0
- package/dist/protocol/transport.d.ts.map +1 -0
- package/dist/protocol/transport.js +120 -0
- package/dist/protocol/transport.js.map +1 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# xiaomi-miio
|
|
2
|
+
|
|
3
|
+
Zero-dependency Node.js TypeScript library for communicating with Xiaomi devices via the miIO protocol.
|
|
4
|
+
|
|
5
|
+
miIO is Xiaomi's local LAN protocol used by many devices (fans, lamps, etc.) for discovery and command/control.
|
|
6
|
+
It is UDP-based, uses a 32-byte header, and encrypts JSON-RPC payloads with AES-128-CBC.
|
|
7
|
+
|
|
8
|
+
This library focuses on the protocol and transport layer only:
|
|
9
|
+
|
|
10
|
+
- No device-specific logic (that lives in the Homey app in this repo)
|
|
11
|
+
- No external dependencies
|
|
12
|
+
- No filesystem access
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Node.js 22+
|
|
17
|
+
- npm 10+
|
|
18
|
+
|
|
19
|
+
## Background and references
|
|
20
|
+
|
|
21
|
+
- miIO binary protocol notes: https://github.com/OpenMiHome/mihome-binary-protocol
|
|
22
|
+
- miIO protocol notes (archived): https://github.com/OpenMiHome/mihome-binary-protocol/blob/master/doc/PROTOCOL.md
|
|
23
|
+
- Xiaomi developer portal: https://developers.xiaomi.com
|
|
24
|
+
- Xiaomi IoT platform: https://iot.mi.com
|
|
25
|
+
- Similar projects:
|
|
26
|
+
- python-miio: https://github.com/rytilahti/python-miio
|
|
27
|
+
- miio (Node.js): https://www.npmjs.com/package/miio
|
|
28
|
+
|
|
29
|
+
## Run locally
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cd xiaomi-miio
|
|
33
|
+
npm install
|
|
34
|
+
npm run build
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The build compiles TypeScript to `dist/` and is required before consuming the library from other packages.
|
|
38
|
+
|
|
39
|
+
Run tests:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run test:dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Tests compile to `dist-test/` and run with Node's built-in test runner.
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
Bedside Lamp 2 CLI (interactive control of a single device):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm run example:bedlamp2
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This will prompt for the device IP and token, perform a handshake, and then let you send simple commands.
|
|
56
|
+
|
|
57
|
+
Discovery CLI (scan the local network for miIO devices via UDP broadcast):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run example:discovery
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This prints each responding device with its IP address, device ID, and stamp. Tokens are optional.
|
|
64
|
+
|
|
65
|
+
## Minimal usage (TypeScript)
|
|
66
|
+
|
|
67
|
+
This example connects to a known device IP and token, turns it on, and sets the speed level.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { MiioDevice, MiioProtocol } from 'xiaomi-miio';
|
|
71
|
+
|
|
72
|
+
const device = new MiioDevice({
|
|
73
|
+
address: '192.168.1.50',
|
|
74
|
+
token: MiioProtocol.tokenFromHex('00112233445566778899aabbccddeeff'),
|
|
75
|
+
model: 'zhimi.fan.sa1',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const info = await device.connect();
|
|
79
|
+
console.log('Device ID:', info.deviceId);
|
|
80
|
+
|
|
81
|
+
await device.call('set_power', ['on']);
|
|
82
|
+
await device.call('set_speed_level', [50]);
|
|
83
|
+
|
|
84
|
+
device.destroy();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Discovery usage (TypeScript)
|
|
88
|
+
|
|
89
|
+
This example broadcasts a discovery packet and prints all devices that respond.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { discoverMiioDevices } from 'xiaomi-miio';
|
|
93
|
+
|
|
94
|
+
const devices = await discoverMiioDevices({ timeout: 3000, includeToken: false });
|
|
95
|
+
for (const device of devices) {
|
|
96
|
+
console.log(`${device.address} id=${device.deviceId} stamp=${device.stamp}`);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Contributors
|
|
101
|
+
|
|
102
|
+
- Martyn de Vries (maintainer)
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MiioTransport, type TransportOptions } from './protocol/transport.js';
|
|
2
|
+
export interface DeviceOptions extends TransportOptions {
|
|
3
|
+
/** Device model identifier (e.g. "yeelink.light.bslamp2"). */
|
|
4
|
+
model?: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface DeviceInfo {
|
|
7
|
+
address: string;
|
|
8
|
+
deviceId: number;
|
|
9
|
+
model?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Base class for miIO devices.
|
|
13
|
+
*
|
|
14
|
+
* Provides the transport layer and common command methods.
|
|
15
|
+
* Subclass this to implement device-specific commands.
|
|
16
|
+
*/
|
|
17
|
+
export declare class MiioDevice {
|
|
18
|
+
protected readonly transport: MiioTransport;
|
|
19
|
+
readonly model?: string | undefined;
|
|
20
|
+
readonly address: string;
|
|
21
|
+
constructor(options: DeviceOptions);
|
|
22
|
+
/** Connect to the device by performing the handshake. */
|
|
23
|
+
connect(): Promise<DeviceInfo>;
|
|
24
|
+
/** Send a raw miIO command. */
|
|
25
|
+
call(method: string, params?: unknown[]): Promise<unknown>;
|
|
26
|
+
/** Query device properties. */
|
|
27
|
+
getProperties(props: string[]): Promise<unknown>;
|
|
28
|
+
/** Disconnect and clean up resources. */
|
|
29
|
+
destroy(): void;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE/E,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,aAAa;IAMlC,yDAAyD;IACnD,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;IASpC,+BAA+B;IACzB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIpE,+BAA+B;IACzB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD,yCAAyC;IACzC,OAAO,IAAI,IAAI;CAGhB"}
|
package/dist/device.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MiioTransport } from './protocol/transport.js';
|
|
2
|
+
/**
|
|
3
|
+
* Base class for miIO devices.
|
|
4
|
+
*
|
|
5
|
+
* Provides the transport layer and common command methods.
|
|
6
|
+
* Subclass this to implement device-specific commands.
|
|
7
|
+
*/
|
|
8
|
+
export class MiioDevice {
|
|
9
|
+
transport;
|
|
10
|
+
model;
|
|
11
|
+
address;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.transport = new MiioTransport(options);
|
|
14
|
+
this.model = options.model;
|
|
15
|
+
this.address = options.address;
|
|
16
|
+
}
|
|
17
|
+
/** Connect to the device by performing the handshake. */
|
|
18
|
+
async connect() {
|
|
19
|
+
const { deviceId } = await this.transport.handshake();
|
|
20
|
+
return {
|
|
21
|
+
address: this.address,
|
|
22
|
+
deviceId,
|
|
23
|
+
model: this.model,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Send a raw miIO command. */
|
|
27
|
+
async call(method, params = []) {
|
|
28
|
+
return this.transport.send(method, params);
|
|
29
|
+
}
|
|
30
|
+
/** Query device properties. */
|
|
31
|
+
async getProperties(props) {
|
|
32
|
+
return this.call('get_prop', props);
|
|
33
|
+
}
|
|
34
|
+
/** Disconnect and clean up resources. */
|
|
35
|
+
destroy() {
|
|
36
|
+
this.transport.destroy();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAyB,MAAM,yBAAyB,CAAC;AAa/E;;;;;GAKG;AACH,MAAM,OAAO,UAAU;IACF,SAAS,CAAgB;IACnC,KAAK,CAAsB;IAC3B,OAAO,CAAS;IAEzB,YAAY,OAAsB;QAChC,IAAI,CAAC,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;QACtD,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ;YACR,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC;IACJ,CAAC;IAED,+BAA+B;IAC/B,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,SAAoB,EAAE;QAC/C,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED,+BAA+B;IAC/B,KAAK,CAAC,aAAa,CAAC,KAAe;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,yCAAyC;IACzC,OAAO;QACL,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { MiioProtocol } from './protocol/protocol.js';
|
|
2
|
+
export { MiioPacket } from './protocol/packet.js';
|
|
3
|
+
export { MiioCrypto } from './protocol/crypto.js';
|
|
4
|
+
export { MiioTransport, type TransportOptions } from './protocol/transport.js';
|
|
5
|
+
export { discoverMiioDevices, type DiscoveryOptions, type MiioDiscoveredDevice } from './protocol/discovery.js';
|
|
6
|
+
export { MiioDevice, type DeviceInfo, type DeviceOptions } from './device.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAChH,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { MiioProtocol } from './protocol/protocol.js';
|
|
2
|
+
export { MiioPacket } from './protocol/packet.js';
|
|
3
|
+
export { MiioCrypto } from './protocol/crypto.js';
|
|
4
|
+
export { MiioTransport } from './protocol/transport.js';
|
|
5
|
+
export { discoverMiioDevices } from './protocol/discovery.js';
|
|
6
|
+
export { MiioDevice } from './device.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAyB,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAoD,MAAM,yBAAyB,CAAC;AAChH,OAAO,EAAE,UAAU,EAAuC,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles miIO protocol encryption and decryption.
|
|
3
|
+
*
|
|
4
|
+
* The miIO protocol uses AES-128-CBC with PKCS#7 padding.
|
|
5
|
+
* - Key = MD5(token)
|
|
6
|
+
* - IV = MD5(Key + token)
|
|
7
|
+
*/
|
|
8
|
+
export declare class MiioCrypto {
|
|
9
|
+
private readonly key;
|
|
10
|
+
private readonly iv;
|
|
11
|
+
constructor(token: Buffer);
|
|
12
|
+
/** Encrypt a plaintext buffer using AES-128-CBC. */
|
|
13
|
+
encrypt(plaintext: Buffer): Buffer;
|
|
14
|
+
/** Decrypt a ciphertext buffer using AES-128-CBC. */
|
|
15
|
+
decrypt(ciphertext: Buffer): Buffer;
|
|
16
|
+
/** Compute MD5 hash of the given data. */
|
|
17
|
+
static md5(data: Buffer): Buffer;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/protocol/crypto.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;gBAEhB,KAAK,EAAE,MAAM;IAQzB,oDAAoD;IACpD,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAKlC,qDAAqD;IACrD,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAKnC,0CAA0C;IAC1C,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAGjC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Handles miIO protocol encryption and decryption.
|
|
4
|
+
*
|
|
5
|
+
* The miIO protocol uses AES-128-CBC with PKCS#7 padding.
|
|
6
|
+
* - Key = MD5(token)
|
|
7
|
+
* - IV = MD5(Key + token)
|
|
8
|
+
*/
|
|
9
|
+
export class MiioCrypto {
|
|
10
|
+
key;
|
|
11
|
+
iv;
|
|
12
|
+
constructor(token) {
|
|
13
|
+
if (token.length !== 16) {
|
|
14
|
+
throw new Error(`Token must be 16 bytes, got ${token.length}`);
|
|
15
|
+
}
|
|
16
|
+
this.key = MiioCrypto.md5(token);
|
|
17
|
+
this.iv = MiioCrypto.md5(Buffer.concat([this.key, token]));
|
|
18
|
+
}
|
|
19
|
+
/** Encrypt a plaintext buffer using AES-128-CBC. */
|
|
20
|
+
encrypt(plaintext) {
|
|
21
|
+
const cipher = crypto.createCipheriv('aes-128-cbc', this.key, this.iv);
|
|
22
|
+
return Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
23
|
+
}
|
|
24
|
+
/** Decrypt a ciphertext buffer using AES-128-CBC. */
|
|
25
|
+
decrypt(ciphertext) {
|
|
26
|
+
const decipher = crypto.createDecipheriv('aes-128-cbc', this.key, this.iv);
|
|
27
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
28
|
+
}
|
|
29
|
+
/** Compute MD5 hash of the given data. */
|
|
30
|
+
static md5(data) {
|
|
31
|
+
return crypto.createHash('md5').update(data).digest();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/protocol/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,OAAO,UAAU;IACJ,GAAG,CAAS;IACZ,EAAE,CAAS;IAE5B,YAAY,KAAa;QACvB,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,oDAAoD;IACpD,OAAO,CAAC,SAAiB;QACvB,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,qDAAqD;IACrD,OAAO,CAAC,UAAkB;QACxB,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,0CAA0C;IAC1C,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACxD,CAAC;CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as dgram from 'node:dgram';
|
|
2
|
+
export interface DiscoveryOptions {
|
|
3
|
+
/** Broadcast address for discovery. Default: 255.255.255.255 */
|
|
4
|
+
address?: string | undefined;
|
|
5
|
+
/** UDP port for miIO discovery. Default: 54321 */
|
|
6
|
+
port?: number | undefined;
|
|
7
|
+
/** Discovery timeout in milliseconds. Default: 5000 */
|
|
8
|
+
timeout?: number | undefined;
|
|
9
|
+
/** Include the token from the Hello response (if present). Default: false */
|
|
10
|
+
includeToken?: boolean | undefined;
|
|
11
|
+
/** Optional factory for creating the UDP socket (for testing). */
|
|
12
|
+
createSocket?: (() => dgram.Socket) | undefined;
|
|
13
|
+
}
|
|
14
|
+
export interface MiioDiscoveredDevice {
|
|
15
|
+
/** Device IP address. */
|
|
16
|
+
address: string;
|
|
17
|
+
/** Device ID from the Hello response. */
|
|
18
|
+
deviceId: number;
|
|
19
|
+
/** Device stamp from the Hello response. */
|
|
20
|
+
stamp: number;
|
|
21
|
+
/** Token from the Hello response (if includeToken is true). */
|
|
22
|
+
token?: Buffer | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Discover miIO devices on the local network via UDP broadcast.
|
|
26
|
+
*
|
|
27
|
+
* Sends a Hello packet and collects all Hello responses until timeout.
|
|
28
|
+
*/
|
|
29
|
+
export declare function discoverMiioDevices(options?: DiscoveryOptions): Promise<MiioDiscoveredDevice[]>;
|
|
30
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/protocol/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAQpC,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,kEAAkE;IAClE,YAAY,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;CACjD;AAED,MAAM,WAAW,oBAAoB;IACnC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CA8DjC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as dgram from 'node:dgram';
|
|
2
|
+
import { MiioPacket } from './packet.js';
|
|
3
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
4
|
+
const DEFAULT_BROADCAST_ADDRESS = '255.255.255.255';
|
|
5
|
+
const DEFAULT_PORT = 54321;
|
|
6
|
+
const EMPTY_TOKEN = Buffer.alloc(16, 0);
|
|
7
|
+
/**
|
|
8
|
+
* Discover miIO devices on the local network via UDP broadcast.
|
|
9
|
+
*
|
|
10
|
+
* Sends a Hello packet and collects all Hello responses until timeout.
|
|
11
|
+
*/
|
|
12
|
+
export async function discoverMiioDevices(options = {}) {
|
|
13
|
+
const address = options.address ?? DEFAULT_BROADCAST_ADDRESS;
|
|
14
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
15
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
16
|
+
const includeToken = options.includeToken ?? false;
|
|
17
|
+
const createSocket = options.createSocket ?? (() => dgram.createSocket('udp4'));
|
|
18
|
+
const socket = createSocket();
|
|
19
|
+
const results = new Map();
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let settled = false;
|
|
22
|
+
const finish = (error) => {
|
|
23
|
+
if (settled)
|
|
24
|
+
return;
|
|
25
|
+
settled = true;
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
socket.removeListener('message', onMessage);
|
|
28
|
+
socket.removeListener('error', onError);
|
|
29
|
+
try {
|
|
30
|
+
socket.close();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore close errors
|
|
34
|
+
}
|
|
35
|
+
if (error) {
|
|
36
|
+
reject(error);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
resolve([...results.values()]);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const timer = setTimeout(() => { finish(); }, timeout);
|
|
43
|
+
const onError = (err) => { finish(err); };
|
|
44
|
+
const onMessage = (msg, rinfo) => {
|
|
45
|
+
try {
|
|
46
|
+
const { deviceId, stamp } = MiioPacket.decode(msg, EMPTY_TOKEN);
|
|
47
|
+
const device = {
|
|
48
|
+
address: rinfo.address,
|
|
49
|
+
deviceId,
|
|
50
|
+
stamp,
|
|
51
|
+
};
|
|
52
|
+
if (includeToken) {
|
|
53
|
+
device.token = MiioPacket.extractToken(msg);
|
|
54
|
+
}
|
|
55
|
+
results.set(rinfo.address, device);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Ignore malformed or non-miIO responses
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
socket.on('message', onMessage);
|
|
62
|
+
socket.on('error', onError);
|
|
63
|
+
socket.bind(0, () => {
|
|
64
|
+
socket.setBroadcast(true);
|
|
65
|
+
const hello = MiioPacket.createHello();
|
|
66
|
+
socket.send(hello, 0, hello.length, port, address, (err) => {
|
|
67
|
+
if (err)
|
|
68
|
+
finish(err);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/protocol/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* miIO binary packet structure.
|
|
3
|
+
*
|
|
4
|
+
* Header (32 bytes):
|
|
5
|
+
* - Magic: 2 bytes (0x2131)
|
|
6
|
+
* - Length: 2 bytes (total packet length including header)
|
|
7
|
+
* - Unknown: 4 bytes (0x00000000, or 0xFFFFFFFF for Hello)
|
|
8
|
+
* - Device ID: 4 bytes (0xFFFFFFFF for Hello)
|
|
9
|
+
* - Stamp: 4 bytes (incrementing counter)
|
|
10
|
+
* - Checksum: 16 bytes (MD5 of header + token + payload, or token in Hello response)
|
|
11
|
+
*/
|
|
12
|
+
export declare class MiioPacket {
|
|
13
|
+
static readonly MAGIC = 8497;
|
|
14
|
+
static readonly HEADER_SIZE = 32;
|
|
15
|
+
/** Create a Hello discovery packet (all 0xFF except magic and length). */
|
|
16
|
+
static createHello(): Buffer;
|
|
17
|
+
/** Encode a JSON command into an encrypted miIO packet. */
|
|
18
|
+
static encode(deviceId: number, stamp: number, token: Buffer, payload: object): Buffer;
|
|
19
|
+
/** Decode a miIO packet, returning parsed header and decrypted payload. */
|
|
20
|
+
static decode(data: Buffer, token: Buffer): {
|
|
21
|
+
deviceId: number;
|
|
22
|
+
stamp: number;
|
|
23
|
+
payload: object | null;
|
|
24
|
+
};
|
|
25
|
+
/** Extract the device token from a Hello response packet. */
|
|
26
|
+
static extractToken(helloResponse: Buffer): Buffer;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=packet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packet.d.ts","sourceRoot":"","sources":["../../src/protocol/packet.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;IACrB,MAAM,CAAC,QAAQ,CAAC,KAAK,QAAU;IAC/B,MAAM,CAAC,QAAQ,CAAC,WAAW,MAAM;IAEjC,0EAA0E;IAC1E,MAAM,CAAC,WAAW,IAAI,MAAM;IAO5B,2DAA2D;IAC3D,MAAM,CAAC,MAAM,CACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,MAAM;IAwBT,2EAA2E;IAC3E,MAAM,CAAC,MAAM,CACX,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IA4B9D,6DAA6D;IAC7D,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM;CAMnD"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { MiioCrypto } from './crypto.js';
|
|
2
|
+
/**
|
|
3
|
+
* miIO binary packet structure.
|
|
4
|
+
*
|
|
5
|
+
* Header (32 bytes):
|
|
6
|
+
* - Magic: 2 bytes (0x2131)
|
|
7
|
+
* - Length: 2 bytes (total packet length including header)
|
|
8
|
+
* - Unknown: 4 bytes (0x00000000, or 0xFFFFFFFF for Hello)
|
|
9
|
+
* - Device ID: 4 bytes (0xFFFFFFFF for Hello)
|
|
10
|
+
* - Stamp: 4 bytes (incrementing counter)
|
|
11
|
+
* - Checksum: 16 bytes (MD5 of header + token + payload, or token in Hello response)
|
|
12
|
+
*/
|
|
13
|
+
export class MiioPacket {
|
|
14
|
+
static MAGIC = 0x2131;
|
|
15
|
+
static HEADER_SIZE = 32;
|
|
16
|
+
/** Create a Hello discovery packet (all 0xFF except magic and length). */
|
|
17
|
+
static createHello() {
|
|
18
|
+
const packet = Buffer.alloc(MiioPacket.HEADER_SIZE, 0xff);
|
|
19
|
+
packet.writeUInt16BE(MiioPacket.MAGIC, 0);
|
|
20
|
+
packet.writeUInt16BE(MiioPacket.HEADER_SIZE, 2);
|
|
21
|
+
return packet;
|
|
22
|
+
}
|
|
23
|
+
/** Encode a JSON command into an encrypted miIO packet. */
|
|
24
|
+
static encode(deviceId, stamp, token, payload) {
|
|
25
|
+
const miiocrypto = new MiioCrypto(token);
|
|
26
|
+
const jsonStr = JSON.stringify(payload);
|
|
27
|
+
const encrypted = miiocrypto.encrypt(Buffer.from(jsonStr, 'utf-8'));
|
|
28
|
+
const packetLength = MiioPacket.HEADER_SIZE + encrypted.length;
|
|
29
|
+
const header = Buffer.alloc(MiioPacket.HEADER_SIZE);
|
|
30
|
+
header.writeUInt16BE(MiioPacket.MAGIC, 0);
|
|
31
|
+
header.writeUInt16BE(packetLength, 2);
|
|
32
|
+
header.writeUInt32BE(0x00000000, 4);
|
|
33
|
+
header.writeUInt32BE(deviceId, 8);
|
|
34
|
+
header.writeUInt32BE(stamp, 12);
|
|
35
|
+
// Checksum placeholder (16 bytes of token for checksum calculation)
|
|
36
|
+
token.copy(header, 16);
|
|
37
|
+
// Calculate MD5 checksum over header + encrypted payload
|
|
38
|
+
const checksum = MiioCrypto.md5(Buffer.concat([header, encrypted]));
|
|
39
|
+
checksum.copy(header, 16);
|
|
40
|
+
return Buffer.concat([header, encrypted]);
|
|
41
|
+
}
|
|
42
|
+
/** Decode a miIO packet, returning parsed header and decrypted payload. */
|
|
43
|
+
static decode(data, token) {
|
|
44
|
+
if (data.length < MiioPacket.HEADER_SIZE) {
|
|
45
|
+
throw new Error(`Packet too short: ${data.length} bytes`);
|
|
46
|
+
}
|
|
47
|
+
const magic = data.readUInt16BE(0);
|
|
48
|
+
if (magic !== MiioPacket.MAGIC) {
|
|
49
|
+
throw new Error(`Invalid magic: 0x${magic.toString(16)}`);
|
|
50
|
+
}
|
|
51
|
+
const length = data.readUInt16BE(2);
|
|
52
|
+
const deviceId = data.readUInt32BE(8);
|
|
53
|
+
const stamp = data.readUInt32BE(12);
|
|
54
|
+
if (length === MiioPacket.HEADER_SIZE) {
|
|
55
|
+
// Hello response — no payload, checksum field contains the token
|
|
56
|
+
return { deviceId, stamp, payload: null };
|
|
57
|
+
}
|
|
58
|
+
const encrypted = data.subarray(MiioPacket.HEADER_SIZE, length);
|
|
59
|
+
const miiocrypto = new MiioCrypto(token);
|
|
60
|
+
const decrypted = miiocrypto.decrypt(encrypted);
|
|
61
|
+
const jsonStr = decrypted.toString('utf-8');
|
|
62
|
+
const payload = JSON.parse(jsonStr);
|
|
63
|
+
return { deviceId, stamp, payload };
|
|
64
|
+
}
|
|
65
|
+
/** Extract the device token from a Hello response packet. */
|
|
66
|
+
static extractToken(helloResponse) {
|
|
67
|
+
if (helloResponse.length < MiioPacket.HEADER_SIZE) {
|
|
68
|
+
throw new Error('Hello response too short');
|
|
69
|
+
}
|
|
70
|
+
return Buffer.from(helloResponse.subarray(16, 32));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=packet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packet.js","sourceRoot":"","sources":["../../src/protocol/packet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;GAUG;AACH,MAAM,OAAO,UAAU;IACrB,MAAM,CAAU,KAAK,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAU,WAAW,GAAG,EAAE,CAAC;IAEjC,0EAA0E;IAC1E,MAAM,CAAC,WAAW;QAChB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC1D,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,2DAA2D;IAC3D,MAAM,CAAC,MAAM,CACX,QAAgB,EAChB,KAAa,EACb,KAAa,EACb,OAAe;QAEf,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAEpE,MAAM,YAAY,GAAG,UAAU,CAAC,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC;QAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAEpD,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAEhC,oEAAoE;QACpE,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAEvB,yDAAyD;QACzD,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;QACpE,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAE1B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,2EAA2E;IAC3E,MAAM,CAAC,MAAM,CACX,IAAY,EACZ,KAAa;QAEb,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,CAAC,MAAM,QAAQ,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,KAAK,KAAK,UAAU,CAAC,KAAK,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,oBAAoB,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAEpC,IAAI,MAAM,KAAK,UAAU,CAAC,WAAW,EAAE,CAAC;YACtC,iEAAiE;YACjE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5C,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAW,CAAC;QAE9C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACtC,CAAC;IAED,6DAA6D;IAC7D,MAAM,CAAC,YAAY,CAAC,aAAqB;QACvC,IAAI,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { MiioCrypto } from './crypto.js';
|
|
2
|
+
export { MiioPacket } from './packet.js';
|
|
3
|
+
export { MiioTransport } from './transport.js';
|
|
4
|
+
export { discoverMiioDevices, type DiscoveryOptions, type MiioDiscoveredDevice } from './discovery.js';
|
|
5
|
+
/**
|
|
6
|
+
* Re-export of the protocol building blocks.
|
|
7
|
+
*
|
|
8
|
+
* The miIO protocol operates as follows:
|
|
9
|
+
* 1. Send a Hello packet to UDP port 54321
|
|
10
|
+
* 2. Receive device ID and stamp from the Hello response
|
|
11
|
+
* 3. Encrypt JSON-RPC commands with AES-128-CBC using the device token
|
|
12
|
+
* 4. Send commands and receive encrypted responses
|
|
13
|
+
*/
|
|
14
|
+
export declare class MiioProtocol {
|
|
15
|
+
/** Default miIO UDP port. */
|
|
16
|
+
static readonly PORT = 54321;
|
|
17
|
+
/** Convert a hex token string to a Buffer. */
|
|
18
|
+
static tokenFromHex(hex: string): Buffer;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEvG;;;;;;;;GAQG;AACH,qBAAa,YAAY;IACvB,6BAA6B;IAC7B,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS;IAE7B,8CAA8C;IAC9C,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAMzC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { MiioCrypto } from './crypto.js';
|
|
2
|
+
export { MiioPacket } from './packet.js';
|
|
3
|
+
export { MiioTransport } from './transport.js';
|
|
4
|
+
export { discoverMiioDevices } from './discovery.js';
|
|
5
|
+
/**
|
|
6
|
+
* Re-export of the protocol building blocks.
|
|
7
|
+
*
|
|
8
|
+
* The miIO protocol operates as follows:
|
|
9
|
+
* 1. Send a Hello packet to UDP port 54321
|
|
10
|
+
* 2. Receive device ID and stamp from the Hello response
|
|
11
|
+
* 3. Encrypt JSON-RPC commands with AES-128-CBC using the device token
|
|
12
|
+
* 4. Send commands and receive encrypted responses
|
|
13
|
+
*/
|
|
14
|
+
export class MiioProtocol {
|
|
15
|
+
/** Default miIO UDP port. */
|
|
16
|
+
static PORT = 54321;
|
|
17
|
+
/** Convert a hex token string to a Buffer. */
|
|
18
|
+
static tokenFromHex(hex) {
|
|
19
|
+
if (hex.length !== 32) {
|
|
20
|
+
throw new Error(`Token hex must be 32 characters, got ${hex.length}`);
|
|
21
|
+
}
|
|
22
|
+
return Buffer.from(hex, 'hex');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAoD,MAAM,gBAAgB,CAAC;AAEvG;;;;;;;;GAQG;AACH,MAAM,OAAO,YAAY;IACvB,6BAA6B;IAC7B,MAAM,CAAU,IAAI,GAAG,KAAK,CAAC;IAE7B,8CAA8C;IAC9C,MAAM,CAAC,YAAY,CAAC,GAAW;QAC7B,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as dgram from 'node:dgram';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
export interface TransportOptions {
|
|
4
|
+
/** Device IP address. */
|
|
5
|
+
address: string;
|
|
6
|
+
/** Device token (16-byte Buffer). */
|
|
7
|
+
token: Buffer;
|
|
8
|
+
/** Command timeout in milliseconds. Default: 5000. */
|
|
9
|
+
timeout?: number | undefined;
|
|
10
|
+
/** Optional factory for creating the UDP socket (for testing). */
|
|
11
|
+
createSocket?: (() => dgram.Socket) | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* UDP transport layer for the miIO protocol.
|
|
15
|
+
*
|
|
16
|
+
* Handles socket management, handshake, command sending,
|
|
17
|
+
* and response correlation via message IDs.
|
|
18
|
+
*/
|
|
19
|
+
export declare class MiioTransport extends EventEmitter {
|
|
20
|
+
private readonly address;
|
|
21
|
+
private readonly token;
|
|
22
|
+
private readonly timeout;
|
|
23
|
+
private readonly createSocket;
|
|
24
|
+
private socket;
|
|
25
|
+
private deviceId;
|
|
26
|
+
private stamp;
|
|
27
|
+
private lastHandshake;
|
|
28
|
+
private messageId;
|
|
29
|
+
private readonly pendingRequests;
|
|
30
|
+
constructor(options: TransportOptions);
|
|
31
|
+
/** Perform the initial handshake to discover the device. */
|
|
32
|
+
handshake(): Promise<{
|
|
33
|
+
deviceId: number;
|
|
34
|
+
stamp: number;
|
|
35
|
+
}>;
|
|
36
|
+
/** Send a miIO JSON-RPC command and wait for the response. */
|
|
37
|
+
send(method: string, params?: unknown[]): Promise<unknown>;
|
|
38
|
+
/** Close the UDP socket and clean up. */
|
|
39
|
+
destroy(): void;
|
|
40
|
+
private ensureSocket;
|
|
41
|
+
private handleMessage;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/protocol/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,MAAM,WAAW,gBAAgB;IAC/B,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,kEAAkE;IAClE,YAAY,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;CACjD;AAED;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,SAAS,CAAK;IAEtB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG5B;gBAEQ,OAAO,EAAE,gBAAgB;IAQrC,4DAA4D;IACtD,SAAS,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IA6B/D,8DAA8D;IACxD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAwBpE,yCAAyC;IACzC,OAAO,IAAI,IAAI;IAaf,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,aAAa;CAqBtB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as dgram from 'node:dgram';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { MiioPacket } from './packet.js';
|
|
4
|
+
const MIIO_PORT = 54321;
|
|
5
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
6
|
+
/**
|
|
7
|
+
* UDP transport layer for the miIO protocol.
|
|
8
|
+
*
|
|
9
|
+
* Handles socket management, handshake, command sending,
|
|
10
|
+
* and response correlation via message IDs.
|
|
11
|
+
*/
|
|
12
|
+
export class MiioTransport extends EventEmitter {
|
|
13
|
+
address;
|
|
14
|
+
token;
|
|
15
|
+
timeout;
|
|
16
|
+
createSocket;
|
|
17
|
+
socket = null;
|
|
18
|
+
deviceId = 0;
|
|
19
|
+
stamp = 0;
|
|
20
|
+
lastHandshake = 0;
|
|
21
|
+
messageId = 1;
|
|
22
|
+
pendingRequests = new Map();
|
|
23
|
+
constructor(options) {
|
|
24
|
+
super();
|
|
25
|
+
this.address = options.address;
|
|
26
|
+
this.token = options.token;
|
|
27
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
28
|
+
this.createSocket = options.createSocket ?? (() => dgram.createSocket('udp4'));
|
|
29
|
+
}
|
|
30
|
+
/** Perform the initial handshake to discover the device. */
|
|
31
|
+
async handshake() {
|
|
32
|
+
const socket = this.ensureSocket();
|
|
33
|
+
const hello = MiioPacket.createHello();
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
reject(new Error(`Handshake timeout after ${this.timeout}ms`));
|
|
37
|
+
}, this.timeout);
|
|
38
|
+
const onMessage = (msg) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
socket.removeListener('message', onMessage);
|
|
41
|
+
try {
|
|
42
|
+
const { deviceId, stamp } = MiioPacket.decode(msg, this.token);
|
|
43
|
+
this.deviceId = deviceId;
|
|
44
|
+
this.stamp = stamp;
|
|
45
|
+
this.lastHandshake = Date.now();
|
|
46
|
+
resolve({ deviceId, stamp });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
socket.on('message', onMessage);
|
|
53
|
+
socket.send(hello, 0, hello.length, MIIO_PORT, this.address);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Send a miIO JSON-RPC command and wait for the response. */
|
|
57
|
+
async send(method, params = []) {
|
|
58
|
+
// Re-handshake if stale (older than 60 seconds)
|
|
59
|
+
if (Date.now() - this.lastHandshake > 60_000) {
|
|
60
|
+
await this.handshake();
|
|
61
|
+
}
|
|
62
|
+
const id = this.messageId++;
|
|
63
|
+
this.stamp++;
|
|
64
|
+
const payload = { id, method, params };
|
|
65
|
+
const packet = MiioPacket.encode(this.deviceId, this.stamp, this.token, payload);
|
|
66
|
+
const socket = this.ensureSocket();
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
this.pendingRequests.delete(id);
|
|
70
|
+
reject(new Error(`Command '${method}' timed out after ${this.timeout}ms`));
|
|
71
|
+
}, this.timeout);
|
|
72
|
+
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
73
|
+
socket.send(packet, 0, packet.length, MIIO_PORT, this.address);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Close the UDP socket and clean up. */
|
|
77
|
+
destroy() {
|
|
78
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
79
|
+
clearTimeout(pending.timer);
|
|
80
|
+
pending.reject(new Error('Transport destroyed'));
|
|
81
|
+
this.pendingRequests.delete(id);
|
|
82
|
+
}
|
|
83
|
+
if (this.socket) {
|
|
84
|
+
this.socket.close();
|
|
85
|
+
this.socket = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
ensureSocket() {
|
|
89
|
+
if (this.socket)
|
|
90
|
+
return this.socket;
|
|
91
|
+
const socket = this.createSocket();
|
|
92
|
+
socket.on('message', (msg) => { this.handleMessage(msg); });
|
|
93
|
+
socket.on('error', (err) => { this.emit('error', err); });
|
|
94
|
+
this.socket = socket;
|
|
95
|
+
return socket;
|
|
96
|
+
}
|
|
97
|
+
handleMessage(data) {
|
|
98
|
+
try {
|
|
99
|
+
const { payload } = MiioPacket.decode(data, this.token);
|
|
100
|
+
if (payload && typeof payload === 'object' && 'id' in payload) {
|
|
101
|
+
const response = payload;
|
|
102
|
+
const pending = this.pendingRequests.get(response.id);
|
|
103
|
+
if (pending) {
|
|
104
|
+
clearTimeout(pending.timer);
|
|
105
|
+
this.pendingRequests.delete(response.id);
|
|
106
|
+
if (response.error) {
|
|
107
|
+
pending.reject(new Error(`miIO error ${response.error.code}: ${response.error.message}`));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
pending.resolve(response.result);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Ignore malformed packets
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/protocol/transport.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@martyndevries/xiaomi-miio",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency Node.js library for Xiaomi miIO protocol",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"!dist/**/*.test.*"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"pretest": "tsc -p tsconfig.test.json",
|
|
22
|
+
"test": "node --test 'dist-test/**/*.test.js'",
|
|
23
|
+
"test:dev": "NODE_OPTIONS='' tsc -p tsconfig.test.json && node --test 'dist-test/**/*.test.js'",
|
|
24
|
+
"test:coverage": "c8 --reporter=text --reporter=json-summary --reporter=lcov --check-coverage --lines 80 --functions 80 --branches 80 --statements 80 node --test 'dist-test/**/*.test.js'",
|
|
25
|
+
"lint": "eslint src/",
|
|
26
|
+
"build:examples": "tsc -p tsconfig.examples.json",
|
|
27
|
+
"example:bedlamp2": "tsc -p tsconfig.examples.json && node dist-examples/examples/bedlamp2-cli.js",
|
|
28
|
+
"example:discovery": "tsc -p tsconfig.examples.json && node dist-examples/examples/discovery-cli.js",
|
|
29
|
+
"clean": "rm -rf dist dist-test dist-examples",
|
|
30
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@semantic-release/exec": "^6.0.3",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"c8": "^10.1.3",
|
|
36
|
+
"eslint": "^9.0.0",
|
|
37
|
+
"semantic-release": "^24.0.0",
|
|
38
|
+
"typescript": "^5.7.0",
|
|
39
|
+
"typescript-eslint": "^8.0.0"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/mvdevries/xiaomi-miio.git"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"registry": "https://registry.npmjs.org",
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"release": {
|
|
50
|
+
"branches": [
|
|
51
|
+
"main"
|
|
52
|
+
],
|
|
53
|
+
"tagFormat": "xiaomi-miio-v${version}",
|
|
54
|
+
"plugins": [
|
|
55
|
+
"@semantic-release/commit-analyzer",
|
|
56
|
+
"@semantic-release/release-notes-generator",
|
|
57
|
+
[
|
|
58
|
+
"@semantic-release/npm",
|
|
59
|
+
{
|
|
60
|
+
"npmPublish": false
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"@semantic-release/exec",
|
|
65
|
+
{
|
|
66
|
+
"publishCmd": "npm publish --access public --provenance"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"@semantic-release/github"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"license": "ISC"
|
|
73
|
+
}
|