diff options
| author | Li Zhineng <[email protected]> | 2025-07-18 07:44:00 +0800 |
|---|---|---|
| committer | Li Zhineng <[email protected]> | 2025-07-18 07:44:00 +0800 |
| commit | 9e52689efff4eede30e2a706dbb4bf3340f168fc (patch) | |
| tree | 736850ce52f04ae4d21c7986683d0841137a6bec /packages | |
| parent | 29a20909bdbf8e36068304f6597efae92845abc1 (diff) | |
| download | airmx-9e52689efff4eede30e2a706dbb4bf3340f168fc.tar.gz airmx-9e52689efff4eede30e2a706dbb4bf3340f168fc.zip | |
control device
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/airmx/src/airmx.ts | 33 | ||||
| -rw-r--r-- | packages/airmx/src/eagle.ts | 17 | ||||
| -rw-r--r-- | packages/airmx/src/message.ts | 19 | ||||
| -rw-r--r-- | packages/airmx/src/types.ts | 8 | ||||
| -rw-r--r-- | packages/airmx/src/util.ts | 13 |
5 files changed, 80 insertions, 10 deletions
diff --git a/packages/airmx/src/airmx.ts b/packages/airmx/src/airmx.ts index aa9fe30..66379e0 100644 --- a/packages/airmx/src/airmx.ts +++ b/packages/airmx/src/airmx.ts @@ -1,9 +1,11 @@ import crypto from 'crypto' import { MqttClient } from 'mqtt' -import { EagleStatus } from './eagle.js' +import { EagleControlMesasge, EagleStatus } from './eagle.js' import { SnowStatus } from './snow.js' -import type { Config, SnowListener, EagleListener } from './types.js' +import type { Config, SnowListener, EagleListener, EagleControlData } from './types.js' import { Command } from './types.js' +import { Signer } from './util.js' +import type { CommandMessage } from './message.js' export class Topic { constructor( @@ -64,12 +66,15 @@ export class Airmx { #client: MqttClient + #signer + constructor( private readonly config: Config ) { this.#client = this.config.mqtt this.#client.on('connect', this.#handleConnect.bind(this)) this.#client.on('message', this.#handleMessage.bind(this)) + this.#signer = new Signer() } onSnowUpdate(callback: SnowListener) { @@ -118,10 +123,7 @@ export class Airmx { } #validateMessage(deviceId: number, message: string, sig: string) { - const device = this.config.devices.find((device) => device.id === deviceId) - if (device === undefined) { - throw new Error(`Could not find the device with ID ${deviceId}.`) - } + const device = this.#getDevice(deviceId) const plainText = message.slice(1, message.lastIndexOf('"sig"')) const calculated = crypto.createHash('md5') .update(plainText) @@ -131,4 +133,23 @@ export class Airmx { throw new Error('Failed to validate the message.') } } + + control(deviceId: number, data: EagleControlData) { + this.#dispatch(deviceId, EagleControlMesasge.make(data)) + } + + #dispatch(deviceId: number, message: CommandMessage<unknown>) { + const device = this.#getDevice(deviceId) + const sig = this.#signer.sign(message, device.key) + const payload = { ...message.payload(), sig } + this.#client.publish(`airmx/01/1/1/0/1/${deviceId}`, JSON.stringify(payload)) + } + + #getDevice(deviceId: number) { + const device = this.config.devices.find((device) => device.id === deviceId) + if (device === undefined) { + throw new Error(`Could not find the device with ID ${deviceId}.`) + } + return device + } } diff --git a/packages/airmx/src/eagle.ts b/packages/airmx/src/eagle.ts index 4c8c3b3..7a1e313 100644 --- a/packages/airmx/src/eagle.ts +++ b/packages/airmx/src/eagle.ts @@ -1,12 +1,12 @@ -import { type Message, type EagleStatusData, EagleMode } from './types.js' +import { CommandMessage } from './message.js' +import type { Message, EagleStatusData, EagleControlData } from './types.js' +import { EagleMode, MessageSource } from './types.js' export class EagleStatus { constructor( public readonly deviceId: number, public readonly message: Message<EagleStatusData> - ) { - // - } + ) {} static from(deviceId: number, message: Message<EagleStatusData>) { if (message.cmdId !== 210) { @@ -104,3 +104,12 @@ export class EagleStatus { return this.message.data.version } } + +export class EagleControlMesasge extends CommandMessage<EagleControlData> { + static make(data: EagleControlData) { + const timestamp = Math.floor(new Date().getTime() / 1000) + return new EagleControlMesasge( + 100, 'control', data, timestamp, MessageSource.App_Android + ) + } +} diff --git a/packages/airmx/src/message.ts b/packages/airmx/src/message.ts new file mode 100644 index 0000000..f9bcf13 --- /dev/null +++ b/packages/airmx/src/message.ts @@ -0,0 +1,19 @@ +export class CommandMessage<T> { + constructor( + readonly commandId: number, + readonly commandName: string, + readonly data: T, + readonly time: number, + readonly from: number + ) {} + + payload() { + return { + cmdId: this.commandId, + name: this.commandName, + time: this.time, + from: this.from, + data: this.data, + } + } +} diff --git a/packages/airmx/src/types.ts b/packages/airmx/src/types.ts index 2ec21a6..98a767a 100644 --- a/packages/airmx/src/types.ts +++ b/packages/airmx/src/types.ts @@ -99,3 +99,11 @@ export interface SnowStatusData { version: string version_type: 'release' } + +export interface EagleControlData { + power: 0 | 1 + heatStatus: 0 | 1 + mode: number + cadr: number + denoise: 0 | 1 +} diff --git a/packages/airmx/src/util.ts b/packages/airmx/src/util.ts new file mode 100644 index 0000000..08400a7 --- /dev/null +++ b/packages/airmx/src/util.ts @@ -0,0 +1,13 @@ +import crypto from 'crypto' +import type { CommandMessage } from './message.js' + +export class Signer { + sign(message: CommandMessage<unknown>, key: string) { + const plainText = JSON.stringify(message.payload()) + return crypto.createHash('md5') + .update(plainText.slice(1, -1)) + .update(',') + .update(key) + .digest('hex') + } +} |
