Skip to content

Commit f7de2e9

Browse files
committed
Humidifier2
1 parent 6c9e5c5 commit f7de2e9

File tree

12 files changed

+276
-52
lines changed

12 files changed

+276
-52
lines changed

BLE.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,19 +1126,20 @@ this.switchBotBLE.on('log', (log) => {
11261126

11271127
The following devices are supported.
11281128

1129-
| Device | BLE Support |
1130-
| ------------------------- | ----------- |
1131-
| SwitchBot Bot | Yes |
1132-
| SwitchBot Curtain | Yes |
1133-
| SwitchBot Meter | Yes |
1134-
| SwitchBot Motion Sensor | Yes |
1135-
| SwitchBot Contact Sensor | Yes |
1136-
| SwitchBot Plug Mini | Yes |
1137-
| SwitchBot Smart Lock | Yes |
1138-
| SwitchBot Smart Lock Pro | Yes |
1139-
| SwitchBot Humidifier | Yes |
1140-
| SwitchBot Color Bulb | Yes |
1141-
| SwitchBot LED Strip Light | Yes |
1129+
| Device | BLE Support |
1130+
| ---------------------------------------------- | ----------- |
1131+
| SwitchBot Bot | Yes |
1132+
| SwitchBot Curtain | Yes |
1133+
| SwitchBot Meter | Yes |
1134+
| SwitchBot Motion Sensor | Yes |
1135+
| SwitchBot Contact Sensor | Yes |
1136+
| SwitchBot Plug Mini | Yes |
1137+
| SwitchBot Smart Lock | Yes |
1138+
| SwitchBot Smart Lock Pro | Yes |
1139+
| SwitchBot Humidifier | Yes |
1140+
| SwitchBot Evaporative Humidifier (Auto-refill) | No |
1141+
| SwitchBot Color Bulb | Yes |
1142+
| SwitchBot LED Strip Light | Yes |
11421143

11431144
### Summary
11441145

OpenAPI.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,18 +167,19 @@ this.switchBotAPI.on('log', (log) => {
167167

168168
The following devices are supported.
169169

170-
| Device | OpenAPI Support | Webhook Support |
171-
| ------------------------- | --------------- | --------------- |
172-
| SwitchBot Bot | Yes | Yes |
173-
| SwitchBot Curtain | Yes | Yes |
174-
| SwitchBot Meter | Yes | Yes |
175-
| SwitchBot Motion Sensor | Yes | Yes |
176-
| SwitchBot Contact Sensor | Yes | Yes |
177-
| SwitchBot Plug Mini | Yes | Yes |
178-
| SwitchBot Smart Lock | Yes | Yes |
179-
| SwitchBot Humidifier | Yes | Yes |
180-
| SwitchBot Color Bulb | Yes | Yes |
181-
| SwitchBot LED Strip Light | Yes | Yes |
170+
| Device | OpenAPI Support | Webhook Support |
171+
| ---------------------------------------------- | --------------- | --------------- |
172+
| SwitchBot Bot | Yes | Yes |
173+
| SwitchBot Curtain | Yes | Yes |
174+
| SwitchBot Meter | Yes | Yes |
175+
| SwitchBot Motion Sensor | Yes | Yes |
176+
| SwitchBot Contact Sensor | Yes | Yes |
177+
| SwitchBot Plug Min | Yes | Yes |
178+
| SwitchBot Smart Lock | Yes | Yes |
179+
| SwitchBot Humidifier | Yes | Yes |
180+
| SwitchBot Evaporative Humidifier (Auto-refill) | Yes | Yes |
181+
| SwitchBot Color Bulb | Yes | Yes |
182+
| SwitchBot LED Strip Light | Yes | Yes |
182183

183184
### Summary
184185

docs/media/BLE.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,19 +1126,20 @@ this.switchBotBLE.on('log', (log) => {
11261126

11271127
The following devices are supported.
11281128

1129-
| Device | BLE Support |
1130-
| ------------------------- | ----------- |
1131-
| SwitchBot Bot | Yes |
1132-
| SwitchBot Curtain | Yes |
1133-
| SwitchBot Meter | Yes |
1134-
| SwitchBot Motion Sensor | Yes |
1135-
| SwitchBot Contact Sensor | Yes |
1136-
| SwitchBot Plug Mini | Yes |
1137-
| SwitchBot Smart Lock | Yes |
1138-
| SwitchBot Smart Lock Pro | Yes |
1139-
| SwitchBot Humidifier | Yes |
1140-
| SwitchBot Color Bulb | Yes |
1141-
| SwitchBot LED Strip Light | Yes |
1129+
| Device | BLE Support |
1130+
| ------------------------------------------------- | ----------- |
1131+
| SwitchBot Bot | Yes |
1132+
| SwitchBot Curtain | Yes |
1133+
| SwitchBot Meter | Yes |
1134+
| SwitchBot Motion Sensor | Yes |
1135+
| SwitchBot Contact Sensor | Yes |
1136+
| SwitchBot Plug Mini | Yes |
1137+
| SwitchBot Smart Lock | Yes |
1138+
| SwitchBot Smart Lock Pro | Yes |
1139+
| SwitchBot Humidifier | Yes |
1140+
| SwitchBot Evaporative Humidifier (Auto-refill) | Yes |
1141+
| SwitchBot Color Bulb | Yes |
1142+
| SwitchBot LED Strip Light | Yes |
11421143

11431144
### Summary
11441145

docs/media/OpenAPI.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,18 +167,19 @@ this.switchBotAPI.on('log', (log) => {
167167

168168
The following devices are supported.
169169

170-
| Device | OpenAPI Support | Webhook Support |
171-
| ------------------------- | --------------- | --------------- |
172-
| SwitchBot Bot | Yes | Yes |
173-
| SwitchBot Curtain | Yes | Yes |
174-
| SwitchBot Meter | Yes | Yes |
175-
| SwitchBot Motion Sensor | Yes | Yes |
176-
| SwitchBot Contact Sensor | Yes | Yes |
177-
| SwitchBot Plug Mini | Yes | Yes |
178-
| SwitchBot Smart Lock | Yes | Yes |
179-
| SwitchBot Humidifier | Yes | Yes |
180-
| SwitchBot Color Bulb | Yes | Yes |
181-
| SwitchBot LED Strip Light | Yes | Yes |
170+
| Device | OpenAPI Support | Webhook Support |
171+
| ------------------------------------------------- | --------------- | --------------- |
172+
| SwitchBot Bot | Yes | Yes |
173+
| SwitchBot Curtain | Yes | Yes |
174+
| SwitchBot Meter | Yes | Yes |
175+
| SwitchBot Motion Sensor | Yes | Yes |
176+
| SwitchBot Contact Sensor | Yes | Yes |
177+
| SwitchBot Plug Mini | Yes | Yes |
178+
| SwitchBot Smart Lock | Yes | Yes |
179+
| SwitchBot Humidifier | Yes | Yes |
180+
| SwitchBot Evaporative Humidifier (Auto-refill) | Yes | Yes |
181+
| SwitchBot Color Bulb | Yes | Yes |
182+
| SwitchBot LED Strip Light | Yes | Yes |
182183

183184
### Summary
184185

src/advertising.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { WoContact } from './device/wocontact.js'
1515
import { WoCurtain } from './device/wocurtain.js'
1616
import { WoHand } from './device/wohand.js'
1717
import { WoHub2 } from './device/wohub2.js'
18+
import { WoHumi2 } from './device/wohumi2.js'
1819
import { WoHumi } from './device/wohumi.js'
1920
import { WoIOSensorTH } from './device/woiosensorth.js'
2021
import { WoLeak } from './device/woleak.js'
@@ -121,6 +122,8 @@ export class Advertising {
121122
return WoCurtain.parseServiceData(serviceData, manufacturerData, emitLog)
122123
case SwitchBotBLEModel.Humidifier:
123124
return WoHumi.parseServiceData(serviceData, emitLog)
125+
case SwitchBotBLEModel.Humidifier2:
126+
return WoHumi2.parseServiceData(serviceData, emitLog)
124127
case SwitchBotBLEModel.Meter:
125128
return WoSensorTH.parseServiceData(serviceData, emitLog)
126129
case SwitchBotBLEModel.MeterPlus:

src/device/wohumi2.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/* Copyright(C) 2024, donavanbecker (https://github.com/donavanbecker). All rights reserved.
2+
*
3+
* wohumi.ts: Switchbot BLE API registration.
4+
*/
5+
import type { humidifier2ServiceData } from '../types/bledevicestatus.js'
6+
import type { NobleTypes } from '../types/types.js'
7+
8+
import { Buffer } from 'node:buffer'
9+
10+
import { SwitchbotDevice } from '../device.js'
11+
import { SwitchBotBLEModel, SwitchBotBLEModelFriendlyName, SwitchBotBLEModelName } from '../types/types.js'
12+
13+
const HUMIDIFIER_COMMAND_HEADER = '5701'
14+
const TURN_ON_KEY = `${HUMIDIFIER_COMMAND_HEADER}0101`
15+
const TURN_OFF_KEY = `${HUMIDIFIER_COMMAND_HEADER}0102`
16+
const INCREASE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0103`
17+
const DECREASE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0104`
18+
const SET_AUTO_MODE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0105`
19+
const SET_MANUAL_MODE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0106`
20+
21+
/**
22+
* Class representing a WoHumi device.
23+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/tree/latest/devicetypes
24+
*/
25+
export class WoHumi2 extends SwitchbotDevice {
26+
constructor(peripheral: NobleTypes['peripheral'], noble: NobleTypes['noble']) {
27+
super(peripheral, noble)
28+
}
29+
30+
/**
31+
* Parses the service data for WoHumi.
32+
* @param {Buffer} serviceData - The service data buffer.
33+
* @param {Function} emitLog - The function to emit log messages.
34+
* @returns {Promise<humidifier2ServiceData | null>} - Parsed service data or null if invalid.
35+
*/
36+
static async parseServiceData(
37+
serviceData: Buffer,
38+
emitLog: (level: string, message: string) => void,
39+
): Promise<humidifier2ServiceData | null> {
40+
if (serviceData.length !== 8) {
41+
emitLog('debugerror', `[parseServiceDataForWoHumi] Buffer length ${serviceData.length} !== 8!`)
42+
return null
43+
}
44+
45+
const byte1 = serviceData.readUInt8(1)
46+
const byte4 = serviceData.readUInt8(4)
47+
48+
const onState = !!(byte1 & 0b10000000) // 1 - on
49+
const autoMode = !!(byte4 & 0b10000000) // 1 - auto
50+
const percentage = byte4 & 0b01111111 // 0-100%, 101/102/103 - Quick gear 1/2/3
51+
const humidity = autoMode ? 0 : percentage === 101 ? 33 : percentage === 102 ? 66 : percentage === 103 ? 100 : percentage
52+
53+
const data: humidifier2ServiceData = {
54+
model: SwitchBotBLEModel.Humidifier2,
55+
modelName: SwitchBotBLEModelName.Humidifier2,
56+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier2,
57+
onState,
58+
autoMode,
59+
percentage: autoMode ? 0 : percentage,
60+
humidity,
61+
}
62+
63+
return data
64+
}
65+
66+
/**
67+
* Sends a command to the humidifier.
68+
* @param {Buffer} reqBuf - The command buffer.
69+
* @returns {Promise<void>}
70+
*/
71+
protected async operateHumi(reqBuf: Buffer): Promise<void> {
72+
const resBuf = await this.command(reqBuf)
73+
const code = resBuf.readUInt8(0)
74+
75+
if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
76+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`)
77+
}
78+
}
79+
80+
/**
81+
* Turns on the humidifier.
82+
* @returns {Promise<void>}
83+
*/
84+
public async turnOn(): Promise<void> {
85+
await this.operateHumi(Buffer.from(TURN_ON_KEY, 'hex'))
86+
}
87+
88+
/**
89+
* Turns off the humidifier.
90+
* @returns {Promise<void>}
91+
*/
92+
public async turnOff(): Promise<void> {
93+
await this.operateHumi(Buffer.from(TURN_OFF_KEY, 'hex'))
94+
}
95+
96+
/**
97+
* Increases the humidifier setting.
98+
* @returns {Promise<void>}
99+
*/
100+
public async increase(): Promise<void> {
101+
await this.operateHumi(Buffer.from(INCREASE_KEY, 'hex'))
102+
}
103+
104+
/**
105+
* Decreases the humidifier setting.
106+
* @returns {Promise<void>}
107+
*/
108+
public async decrease(): Promise<void> {
109+
await this.operateHumi(Buffer.from(DECREASE_KEY, 'hex'))
110+
}
111+
112+
/**
113+
* Sets the humidifier to auto mode.
114+
* @returns {Promise<void>}
115+
*/
116+
public async setAutoMode(): Promise<void> {
117+
await this.operateHumi(Buffer.from(SET_AUTO_MODE_KEY, 'hex'))
118+
}
119+
120+
/**
121+
* Sets the humidifier to manual mode.
122+
* @returns {Promise<void>}
123+
*/
124+
public async setManualMode(): Promise<void> {
125+
await this.operateHumi(Buffer.from(SET_MANUAL_MODE_KEY, 'hex'))
126+
}
127+
128+
/**
129+
* Sets the humidifier level.
130+
* @param {number} level - The level to set (0-100).
131+
* @returns {Promise<void>}
132+
*/
133+
public async percentage(level: number): Promise<void> {
134+
if (level < 0 || level > 100) {
135+
throw new Error('Level must be between 0 and 100')
136+
}
137+
const levelKey = `${HUMIDIFIER_COMMAND_HEADER}0107${level.toString(16).padStart(2, '0')}`
138+
await this.operateHumi(Buffer.from(levelKey, 'hex'))
139+
}
140+
}

src/switchbot-ble.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { WoContact } from './device/wocontact.js'
1515
import { WoCurtain } from './device/wocurtain.js'
1616
import { WoHand } from './device/wohand.js'
1717
import { WoHub2 } from './device/wohub2.js'
18+
import { WoHumi2 } from './device/wohumi2.js'
1819
import { WoHumi } from './device/wohumi.js'
1920
import { WoIOSensorTH } from './device/woiosensorth.js'
2021
import { WoKeypad } from './device/wokeypad.js'
@@ -227,6 +228,7 @@ export class SwitchBotBLE extends EventEmitter {
227228
case SwitchBotBLEModel.Curtain:
228229
case SwitchBotBLEModel.Curtain3: return new WoCurtain(peripheral, this.noble)
229230
case SwitchBotBLEModel.Humidifier: return new WoHumi(peripheral, this.noble)
231+
case SwitchBotBLEModel.Humidifier2: return new WoHumi2(peripheral, this.noble)
230232
case SwitchBotBLEModel.Meter: return new WoSensorTH(peripheral, this.noble)
231233
case SwitchBotBLEModel.MeterPlus: return new WoSensorTHPlus(peripheral, this.noble)
232234
case SwitchBotBLEModel.MeterPro: return new WoSensorTHPro(peripheral, this.noble)

src/test/wohumi2.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { NobleTypes } from '../types/types.js'
2+
3+
import { Buffer } from 'node:buffer'
4+
5+
import { describe, expect, it } from 'vitest'
6+
7+
import { SwitchbotDevice } from '../device.js'
8+
import { WoHumi2 } from '../device/wohumi2.js'
9+
10+
describe('woHumi', () => {
11+
let wohumi: WoHumi2
12+
let mockPeripheral: NobleTypes['peripheral']
13+
let mockNoble: NobleTypes['noble']
14+
15+
beforeEach(() => {
16+
mockPeripheral = {} as NobleTypes['peripheral']
17+
mockNoble = {} as NobleTypes['noble']
18+
wohumi = new WoHumi2(mockPeripheral, mockNoble)
19+
jest.spyOn(SwitchbotDevice.prototype, 'command').mockResolvedValue(Buffer.from([0x01, 0x00, 0x00]))
20+
})
21+
22+
afterEach(() => {
23+
jest.restoreAllMocks()
24+
})
25+
26+
describe('percentage', () => {
27+
it('should throw an error if level is less than 0', async () => {
28+
await expect(wohumi.percentage(-1)).rejects.toThrow('Level must be between 0 and 100')
29+
})
30+
31+
it('should throw an error if level is greater than 100', async () => {
32+
await expect(wohumi.percentage(101)).rejects.toThrow('Level must be between 0 and 100')
33+
})
34+
35+
it('should send the correct command for a valid level', async () => {
36+
const level = 50
37+
const expectedCommand = Buffer.from(`57010107${level.toString(16).padStart(2, '0')}`, 'hex')
38+
const operateHumiSpy = jest.spyOn(wohumi as any, 'operateHumi')
39+
40+
await wohumi.percentage(level)
41+
42+
expect(operateHumiSpy).toHaveBeenCalledWith(expectedCommand)
43+
})
44+
})
45+
})

src/types/bledevicestatus.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,16 @@ export type humidifierServiceData = serviceData & {
312312
humidity: number
313313
}
314314

315+
export type humidifier2ServiceData = serviceData & {
316+
model: SwitchBotBLEModel.Humidifier2
317+
modelName: SwitchBotBLEModelName.Humidifier2
318+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier2
319+
onState: boolean
320+
autoMode: boolean
321+
percentage: number
322+
humidity: number
323+
}
324+
315325
export type robotVacuumCleanerServiceData = serviceData & {
316326
model: SwitchBotBLEModel.Unknown
317327
modelName: SwitchBotBLEModelName.Unknown

src/types/devicestatus.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,17 @@ export type humidifierStatus = deviceStatus & {
165165
lackWater: boolean
166166
}
167167

168+
export type humidifier2Status = deviceStatus & {
169+
power: string
170+
humidity: number
171+
temperature: number
172+
nebulizationEfficiency: number
173+
auto: boolean
174+
childLock: boolean
175+
sound: boolean
176+
lackWater: boolean
177+
}
178+
168179
export type blindTiltStatus = deviceStatus & {
169180
calibrate: boolean
170181
battery: number

0 commit comments

Comments
 (0)