summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/airmx/src/airmx.ts33
-rw-r--r--packages/airmx/src/eagle.ts17
-rw-r--r--packages/airmx/src/message.ts19
-rw-r--r--packages/airmx/src/types.ts8
-rw-r--r--packages/airmx/src/util.ts13
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')
+ }
+}