diff --git a/src/api/MysaApiClient.ts b/src/api/MysaApiClient.ts index 0ca1c07..9b040bc 100644 --- a/src/api/MysaApiClient.ts +++ b/src/api/MysaApiClient.ts @@ -25,7 +25,7 @@ import { MqttPublishError, MysaApiError, UnauthenticatedError } from './Errors'; import { Logger, VoidLogger } from './Logger'; import { MysaApiClientEventTypes } from './MysaApiClientEventTypes'; import { MysaApiClientOptions } from './MysaApiClientOptions'; -import { MysaDeviceMode } from './MysaDeviceMode'; +import { MysaDeviceMode, MysaFanSpeedMode } from './MysaDeviceMode'; dayjs.extend(duration); @@ -357,15 +357,20 @@ export class MysaApiClient { * * // Set temperature and mode * await client.setDeviceState('device123', 20, 'heat'); + * + * // Set fan speed + * await client.setDeviceState('device123', undefined, undefined, 'auto'); * ``` * * @param deviceId - The ID of the device to control. * @param setPoint - The target temperature set point (optional). - * @param mode - The operating mode to set ('off', 'heat', or undefined to leave unchanged). + * @param mode - The operating mode to set (one of MysaDeviceMode values, or undefined to leave unchanged). + * @param fanSpeed - The fan speed mode to set ('low', 'medium', 'high', 'max', 'auto', or undefined to leave + * unchanged). * @throws {@link UnauthenticatedError} When the user is not authenticated. * @throws {@link Error} When MQTT connection or command sending fails. */ - async setDeviceState(deviceId: string, setPoint?: number, mode?: MysaDeviceMode) { + async setDeviceState(deviceId: string, setPoint?: number, mode?: MysaDeviceMode, fanSpeed?: MysaFanSpeedMode) { this._logger.debug(`Setting device state for '${deviceId}'`); if (!this._cachedDevices) { @@ -380,6 +385,9 @@ export class MysaApiClient { const now = dayjs(); this._logger.debug(`Sending request to set device state for '${deviceId}'...`); + const modeMap = { off: 1, auto: 2, heat: 3, cool: 4, fan_only: 5, dry: 6 }; + const fanSpeedMap = { auto: 1, low: 3, medium: 5, high: 7, max: 8 }; + const payload = serializeMqttPayload({ msg: InMessageType.CHANGE_DEVICE_STATE, id: now.valueOf(), @@ -398,16 +406,19 @@ export class MysaApiClient { ver: 1, type: device.Model.startsWith('BB-V1') ? 1 - : device.Model.startsWith('BB-V2') - ? device.Model.endsWith('-L') - ? 5 - : 4 - : 0, + : device.Model.startsWith('AC-V1') + ? 2 + : device.Model.startsWith('BB-V2') + ? device.Model.endsWith('-L') + ? 5 + : 4 + : 0, cmd: [ { tm: -1, sp: setPoint, - md: mode === 'off' ? 1 : mode === 'heat' ? 3 : undefined + md: mode ? modeMap[mode] : undefined, + fn: fanSpeed ? fanSpeedMap[fanSpeed] : undefined } ] } @@ -792,13 +803,31 @@ export class MysaApiClient { }); break; - case OutMessageType.DEVICE_STATE_CHANGE: + case OutMessageType.DEVICE_STATE_CHANGE: { + const modeMap: Record = { + 1: 'off', + 2: 'auto', + 3: 'heat', + 4: 'cool', + 5: 'fan_only', + 6: 'dry' + }; + const fanSpeedMap: Record = { + 1: 'auto', + 3: 'low', + 5: 'medium', + 7: 'high', + 8: 'max' + }; + this.emitter.emit('stateChanged', { deviceId: parsedPayload.src.ref, - mode: parsedPayload.body.state.md === 1 ? 'off' : parsedPayload.body.state.md === 3 ? 'heat' : undefined, - setPoint: parsedPayload.body.state.sp + mode: parsedPayload.body.state.md ? modeMap[parsedPayload.body.state.md] : undefined, + setPoint: parsedPayload.body.state.sp, + fanSpeed: parsedPayload.body.state.fn !== undefined ? fanSpeedMap[parsedPayload.body.state.fn] : undefined }); break; + } } } } catch (error) { diff --git a/src/api/MysaDeviceMode.ts b/src/api/MysaDeviceMode.ts index f1f791a..5635141 100644 --- a/src/api/MysaDeviceMode.ts +++ b/src/api/MysaDeviceMode.ts @@ -4,4 +4,11 @@ * Defines the possible operational states that a Mysa thermostat or heating device can be set to. These modes control * the device's heating behavior and power consumption. */ -export type MysaDeviceMode = 'off' | 'heat'; +export type MysaDeviceMode = 'off' | 'heat' | 'cool' | 'dry' | 'fan_only' | 'auto'; + +/** + * Union type representing the available fan speed modes for Mysa devices. + * + * Defines the possible fan speed states that a Mysa thermostat device can be set to. + */ +export type MysaFanSpeedMode = 'auto' | 'low' | 'medium' | 'high' | 'max'; diff --git a/src/api/events/StateChange.ts b/src/api/events/StateChange.ts index 3868c94..e87d58f 100644 --- a/src/api/events/StateChange.ts +++ b/src/api/events/StateChange.ts @@ -1,4 +1,4 @@ -import { MysaDeviceMode } from '@/api/MysaDeviceMode'; +import { MysaDeviceMode, MysaFanSpeedMode } from '@/api/MysaDeviceMode'; /** * Interface representing a device state change event for a Mysa device. @@ -14,4 +14,6 @@ export interface StateChange { mode?: MysaDeviceMode; /** Current temperature setpoint after the state change */ setPoint: number; + /** Optional fan speed (1 = auto, 3 = low, 5 = medium, 7 = high, 8 = max). AC only */ + fanSpeed?: MysaFanSpeedMode; } diff --git a/src/types/mqtt/in/ChangeDeviceState.ts b/src/types/mqtt/in/ChangeDeviceState.ts index a81cf6c..fc8539c 100644 --- a/src/types/mqtt/in/ChangeDeviceState.ts +++ b/src/types/mqtt/in/ChangeDeviceState.ts @@ -36,6 +36,8 @@ export interface ChangeDeviceState extends MsgPayload; /** Lock status */ Lock: TimestampedValue; + /** Fan speed */ + FanSpeed?: TimestampedValue; } /**