@homebridge-plugins/homebridge-govee 11.32.2-beta.2 → 11.32.2-beta.21
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.
- package/CHANGELOG.md +31 -0
- package/config.schema.json +1 -1
- package/lib/connection/ble.js +26 -1
- package/lib/connection/openapi.js +18 -0
- package/lib/device/appliance-basic.js +377 -0
- package/lib/device/dehumidifier-H7150.js +9 -3
- package/lib/device/diffuser-H7161.js +7 -1
- package/lib/device/fan-H1370.js +7 -1
- package/lib/device/fan-H7100.js +36 -4
- package/lib/device/fan-H7105.js +7 -1
- package/lib/device/fan-H7107.js +7 -1
- package/lib/device/heater1a.js +52 -12
- package/lib/device/heater1b.js +14 -3
- package/lib/device/heater2.js +7 -1
- package/lib/device/humidifier-H7140.js +7 -1
- package/lib/device/humidifier-H7141.js +7 -1
- package/lib/device/humidifier-H7142.js +7 -1
- package/lib/device/humidifier-H7143.js +7 -1
- package/lib/device/humidifier-H7160.js +7 -1
- package/lib/device/ice-maker-H7172.js +19 -7
- package/lib/device/index.js +44 -51
- package/lib/device/kettle.js +7 -1
- package/lib/device/outlet-single.js +7 -1
- package/lib/device/purifier-H7120.js +14 -25
- package/lib/device/purifier-H7122.js +7 -1
- package/lib/device/purifier-H7123.js +9 -3
- package/lib/device/purifier-H7126.js +7 -1
- package/lib/device/{sensor-button.js → sensor-basic.js} +14 -2
- package/lib/device/sensor-presence.js +7 -1
- package/lib/device/sensor-thermo4.js +13 -2
- package/lib/device/switch-double.js +34 -15
- package/lib/device/switch-triple.js +36 -17
- package/lib/device/template.js +15 -2
- package/lib/platform.js +83 -799
- package/lib/utils/capabilities.js +220 -0
- package/lib/utils/command-builder.js +6 -4
- package/lib/utils/config-parser.js +280 -0
- package/lib/utils/constants.js +120 -8
- package/lib/utils/decode.js +4 -1
- package/lib/utils/device-capabilities.js +77 -3
- package/lib/utils/device-context.js +179 -0
- package/lib/utils/device-merge.js +251 -0
- package/lib/utils/dispatch.js +159 -0
- package/lib/utils/functions.js +9 -0
- package/lib/utils/lang-en.js +0 -2
- package/lib/utils/report-unknown.js +156 -0
- package/package.json +1 -1
- package/lib/device/dehumidifier-H7151.js +0 -189
- package/lib/device/dehumidifier-H7152.js +0 -180
- package/lib/device/diffuser-H7162.js +0 -123
- package/lib/device/fan-H1310.js +0 -284
- package/lib/device/fan-H7101.js +0 -340
- package/lib/device/fan-H7102.js +0 -284
- package/lib/device/fan-H7106.js +0 -284
- package/lib/device/fan-H7111.js +0 -345
- package/lib/device/humidifier-H7145.js +0 -187
- package/lib/device/humidifier-H7147.js +0 -187
- package/lib/device/humidifier-H7148.js +0 -187
- package/lib/device/humidifier-H7149.js +0 -187
- package/lib/device/humidifier-H714E.js +0 -187
- package/lib/device/ice-maker-H717D.js +0 -218
- package/lib/device/ice-maker-H8120.js +0 -218
- package/lib/device/outlet-double.js +0 -188
- package/lib/device/outlet-triple.js +0 -160
- package/lib/device/purifier-H7121.js +0 -406
- package/lib/device/purifier-H7124.js +0 -504
- package/lib/device/purifier-H7127.js +0 -359
- package/lib/device/purifier-H7128.js +0 -359
- package/lib/device/purifier-H7129.js +0 -359
- package/lib/device/purifier-H712C.js +0 -359
- package/lib/device/sensor-contact.js +0 -22
- package/lib/utils/ble-timeouts.test.js +0 -44
- package/lib/utils/functions.test.js +0 -292
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
base64ToHex,
|
|
3
|
-
getTwoItemPosition,
|
|
4
|
-
hexToTwoItems,
|
|
5
|
-
parseError,
|
|
6
|
-
} from '../utils/functions.js'
|
|
7
|
-
import platformLang from '../utils/lang-en.js'
|
|
8
|
-
|
|
9
|
-
export default class {
|
|
10
|
-
constructor(platform, accessory) {
|
|
11
|
-
// Set up variables from the platform
|
|
12
|
-
this.hapChar = platform.api.hap.Characteristic
|
|
13
|
-
this.hapErr = platform.api.hap.HapStatusError
|
|
14
|
-
this.hapServ = platform.api.hap.Service
|
|
15
|
-
this.platform = platform
|
|
16
|
-
|
|
17
|
-
// Set up variables from the accessory
|
|
18
|
-
this.accessory = accessory
|
|
19
|
-
|
|
20
|
-
// Speed codes
|
|
21
|
-
this.value2Code = {
|
|
22
|
-
1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
|
|
23
|
-
2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
|
|
24
|
-
3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
|
|
25
|
-
4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
|
|
26
|
-
5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
|
|
27
|
-
6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
|
|
28
|
-
7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
|
|
29
|
-
8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Migrate old %-rotation speed to unitless
|
|
33
|
-
const existingFanService = this.accessory.getService(this.hapServ.Fan)
|
|
34
|
-
if (existingFanService) {
|
|
35
|
-
if (existingFanService.getCharacteristic(this.hapChar.RotationSpeed).props.unit === 'percentage') {
|
|
36
|
-
this.accessory.removeService(existingFanService)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Add the fan service if it doesn't already exist
|
|
41
|
-
this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
|
|
42
|
-
|
|
43
|
-
// Add the set handler to the fan on/off characteristic
|
|
44
|
-
this.service
|
|
45
|
-
.getCharacteristic(this.hapChar.On)
|
|
46
|
-
.onSet(async value => this.internalStateUpdate(value))
|
|
47
|
-
this.cacheState = this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
|
|
48
|
-
|
|
49
|
-
// Add the set handler to the fan rotation speed characteristic
|
|
50
|
-
this.service
|
|
51
|
-
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
52
|
-
.setProps({
|
|
53
|
-
maxValue: 8,
|
|
54
|
-
minStep: 1,
|
|
55
|
-
minValue: 0,
|
|
56
|
-
unit: 'unitless',
|
|
57
|
-
})
|
|
58
|
-
.onSet(async value => this.internalSpeedUpdate(value))
|
|
59
|
-
this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
|
|
60
|
-
|
|
61
|
-
// Output the customised options to the log
|
|
62
|
-
const opts = JSON.stringify({})
|
|
63
|
-
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async internalStateUpdate(value) {
|
|
67
|
-
try {
|
|
68
|
-
const newValue = value ? 'on' : 'off'
|
|
69
|
-
|
|
70
|
-
// Don't continue if the new value is the same as before
|
|
71
|
-
if (this.cacheState === newValue) {
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Send the request to the platform sender function
|
|
76
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
77
|
-
cmd: 'stateHumi',
|
|
78
|
-
value: value ? 1 : 0,
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// Cache the new state and log if appropriate
|
|
82
|
-
this.cacheState = newValue
|
|
83
|
-
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
84
|
-
} catch (err) {
|
|
85
|
-
// Catch any errors during the process
|
|
86
|
-
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
87
|
-
|
|
88
|
-
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
89
|
-
setTimeout(() => {
|
|
90
|
-
this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
|
|
91
|
-
}, 2000)
|
|
92
|
-
throw new this.hapErr(-70402)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async internalSpeedUpdate(value) {
|
|
97
|
-
try {
|
|
98
|
-
// Don't continue if the speed is 0
|
|
99
|
-
if (value === 0) {
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Don't continue if the speed value won't have effect
|
|
104
|
-
if (value === this.cacheSpeed) {
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Get the scene code for this value
|
|
109
|
-
const newCode = this.value2Code[value]
|
|
110
|
-
|
|
111
|
-
// Send the request to the platform sender function
|
|
112
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
113
|
-
cmd: 'ptReal',
|
|
114
|
-
value: newCode,
|
|
115
|
-
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
116
|
-
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: 1, modeValue: value } }
|
|
117
|
-
: undefined,
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
// Cache the new state and log if appropriate
|
|
121
|
-
this.cacheSpeed = value
|
|
122
|
-
this.accessory.log(`${platformLang.curSpeed} [${value}]`)
|
|
123
|
-
} catch (err) {
|
|
124
|
-
// Catch any errors during the process
|
|
125
|
-
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
126
|
-
|
|
127
|
-
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
128
|
-
setTimeout(() => {
|
|
129
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
130
|
-
}, 2000)
|
|
131
|
-
throw new this.hapErr(-70402)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
externalUpdate(params) {
|
|
136
|
-
// Check for an ON/OFF change
|
|
137
|
-
if (params.state && params.state !== this.cacheState) {
|
|
138
|
-
this.cacheState = params.state
|
|
139
|
-
this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
|
|
140
|
-
|
|
141
|
-
// Log the change
|
|
142
|
-
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Handle OpenAPI workMode (speed)
|
|
146
|
-
if (params.workMode) {
|
|
147
|
-
const speed = params.workMode.modeValue
|
|
148
|
-
if (speed >= 1 && speed <= 8) {
|
|
149
|
-
this.cacheSpeed = speed
|
|
150
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Check for some other scene/mode change
|
|
155
|
-
(params.commands || []).forEach((command) => {
|
|
156
|
-
const hexString = base64ToHex(command)
|
|
157
|
-
const hexParts = hexToTwoItems(hexString)
|
|
158
|
-
|
|
159
|
-
// Return now if not a device query update code
|
|
160
|
-
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
|
|
165
|
-
|
|
166
|
-
switch (deviceFunction) {
|
|
167
|
-
case '0501':
|
|
168
|
-
case '0502': {
|
|
169
|
-
// Speed update
|
|
170
|
-
const newSpeedRaw = getTwoItemPosition(hexParts, 4)
|
|
171
|
-
const newSpeedValue = Number.parseInt(newSpeedRaw, 16)
|
|
172
|
-
if (newSpeedValue >= 1 && newSpeedValue <= 8) {
|
|
173
|
-
if (newSpeedValue !== this.cacheSpeed) {
|
|
174
|
-
this.cacheSpeed = newSpeedValue
|
|
175
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
176
|
-
this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
break
|
|
180
|
-
}
|
|
181
|
-
default:
|
|
182
|
-
this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
183
|
-
break
|
|
184
|
-
}
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { base64ToHex, getTwoItemPosition, hexToTwoItems, parseError } from '../utils/functions.js'
|
|
2
|
-
import platformLang from '../utils/lang-en.js'
|
|
3
|
-
|
|
4
|
-
export default class {
|
|
5
|
-
constructor(platform, accessory) {
|
|
6
|
-
// Set up variables from the platform
|
|
7
|
-
this.hapChar = platform.api.hap.Characteristic
|
|
8
|
-
this.hapErr = platform.api.hap.HapStatusError
|
|
9
|
-
this.hapServ = platform.api.hap.Service
|
|
10
|
-
this.platform = platform
|
|
11
|
-
|
|
12
|
-
// Set up variables from the accessory
|
|
13
|
-
this.accessory = accessory
|
|
14
|
-
|
|
15
|
-
// Ice size codes: small=1, medium=2, large=3 (matching OpenAPI workMode values)
|
|
16
|
-
this.sizeCodes = {
|
|
17
|
-
1: 'MwUDAAAAAAAAAAAAAAAAAAAAADU=', // small
|
|
18
|
-
2: 'MwUCAAAAAAAAAAAAAAAAAAAAADQ=', // medium
|
|
19
|
-
3: 'MwUBAAAAAAAAAAAAAAAAAAAAADc=', // large
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
this.sizeLabels = {
|
|
23
|
-
1: 'small',
|
|
24
|
-
2: 'medium',
|
|
25
|
-
3: 'large',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Remove old switch service if migrating
|
|
29
|
-
if (this.accessory.getService(this.hapServ.Switch)) {
|
|
30
|
-
this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Add the fan service for ice size control
|
|
34
|
-
this.service = this.accessory.getService(this.hapServ.Fanv2)
|
|
35
|
-
|| this.accessory.addService(this.hapServ.Fanv2)
|
|
36
|
-
|
|
37
|
-
// Add the set handler to the on/off characteristic
|
|
38
|
-
this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
|
|
39
|
-
await this.internalStateUpdate(value)
|
|
40
|
-
})
|
|
41
|
-
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
42
|
-
|
|
43
|
-
// Add the set handler to the rotation speed characteristic (ice size)
|
|
44
|
-
this.service
|
|
45
|
-
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
46
|
-
.setProps({
|
|
47
|
-
maxValue: 3,
|
|
48
|
-
minStep: 1,
|
|
49
|
-
minValue: 0,
|
|
50
|
-
unit: 'unitless',
|
|
51
|
-
})
|
|
52
|
-
.onSet(async value => this.internalSizeUpdate(value))
|
|
53
|
-
this.cacheSize = this.service.getCharacteristic(this.hapChar.RotationSpeed).value || 2
|
|
54
|
-
|
|
55
|
-
// Keep the characteristic in step with the cache. Without this an accessory
|
|
56
|
-
// that has never had a size set reports Active=1 with RotationSpeed=0, which
|
|
57
|
-
// is contradictory and which the Home app renders as "0".
|
|
58
|
-
if (this.service.getCharacteristic(this.hapChar.RotationSpeed).value !== this.cacheSize) {
|
|
59
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Output the customised options to the log
|
|
63
|
-
const opts = JSON.stringify({})
|
|
64
|
-
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async internalStateUpdate(value) {
|
|
68
|
-
try {
|
|
69
|
-
const newState = value ? 'on' : 'off'
|
|
70
|
-
|
|
71
|
-
// Don't continue if the new value is the same as before
|
|
72
|
-
if (newState === this.cacheState) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (value) {
|
|
77
|
-
// Turn on with current cached ice size
|
|
78
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
79
|
-
cmd: 'ptReal',
|
|
80
|
-
value: this.sizeCodes[this.cacheSize],
|
|
81
|
-
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
82
|
-
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: this.cacheSize, modeValue: 0 } }
|
|
83
|
-
: undefined,
|
|
84
|
-
})
|
|
85
|
-
} else {
|
|
86
|
-
// Turn off
|
|
87
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
88
|
-
cmd: 'ptReal',
|
|
89
|
-
value: 'MxkAAAAAAAAAAAAAAAAAAAAAACo=',
|
|
90
|
-
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
91
|
-
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: 0, modeValue: 0 } }
|
|
92
|
-
: undefined,
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Cache the new state and log if appropriate
|
|
97
|
-
if (this.cacheState !== newState) {
|
|
98
|
-
this.cacheState = newState
|
|
99
|
-
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
100
|
-
}
|
|
101
|
-
} catch (err) {
|
|
102
|
-
// Catch any errors during the process
|
|
103
|
-
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
104
|
-
|
|
105
|
-
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
106
|
-
setTimeout(() => {
|
|
107
|
-
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
108
|
-
}, 2000)
|
|
109
|
-
throw new this.hapErr(-70402)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async internalSizeUpdate(value) {
|
|
114
|
-
try {
|
|
115
|
-
// 0 is not a valid ice size. Put the slider back, otherwise HomeKit keeps 0
|
|
116
|
-
// while the device carries on at cacheSize - and that 0 is what gets written
|
|
117
|
-
// to the accessory cache, so it returns on the next restart. Deferred for the
|
|
118
|
-
// same reason as the error reverts below: an immediate update would be
|
|
119
|
-
// overwritten by the value HomeKit is currently writing.
|
|
120
|
-
if (value === 0) {
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
123
|
-
}, 1000)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Don't continue if the new value is the same as before
|
|
128
|
-
if (value === this.cacheSize) {
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
133
|
-
cmd: 'ptReal',
|
|
134
|
-
value: this.sizeCodes[value],
|
|
135
|
-
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
136
|
-
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: value, modeValue: 0 } }
|
|
137
|
-
: undefined,
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// Cache the new state and log if appropriate
|
|
141
|
-
this.cacheSize = value
|
|
142
|
-
this.cacheState = 'on'
|
|
143
|
-
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
144
|
-
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[value]}]`)
|
|
145
|
-
} catch (err) {
|
|
146
|
-
// Catch any errors during the process
|
|
147
|
-
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
148
|
-
|
|
149
|
-
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
150
|
-
setTimeout(() => {
|
|
151
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
152
|
-
}, 2000)
|
|
153
|
-
throw new this.hapErr(-70402)
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
externalUpdate(params) {
|
|
158
|
-
// Handle OpenAPI workMode
|
|
159
|
-
if (params.workMode) {
|
|
160
|
-
const mode = params.workMode.workMode
|
|
161
|
-
if (mode > 0 && mode <= 3) {
|
|
162
|
-
if (this.cacheState !== 'on') {
|
|
163
|
-
this.cacheState = 'on'
|
|
164
|
-
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
165
|
-
this.accessory.log(`${platformLang.curState} [on]`)
|
|
166
|
-
}
|
|
167
|
-
if (this.cacheSize !== mode) {
|
|
168
|
-
this.cacheSize = mode
|
|
169
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
170
|
-
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[this.cacheSize]}]`)
|
|
171
|
-
}
|
|
172
|
-
} else if (mode === 0) {
|
|
173
|
-
if (this.cacheState !== 'off') {
|
|
174
|
-
this.cacheState = 'off'
|
|
175
|
-
this.service.updateCharacteristic(this.hapChar.Active, 0)
|
|
176
|
-
this.accessory.log(`${platformLang.curState} [off]`)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Check for some other scene/mode change
|
|
182
|
-
(params.commands || []).forEach((command) => {
|
|
183
|
-
const hexString = base64ToHex(command)
|
|
184
|
-
const hexParts = hexToTwoItems(hexString)
|
|
185
|
-
|
|
186
|
-
// Return now if not a device query update code
|
|
187
|
-
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
|
|
192
|
-
|
|
193
|
-
switch (deviceFunction) {
|
|
194
|
-
case 'aa19': {
|
|
195
|
-
// On/Off
|
|
196
|
-
const newState = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
197
|
-
if (this.cacheState !== newState) {
|
|
198
|
-
this.cacheState = newState
|
|
199
|
-
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
200
|
-
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
201
|
-
}
|
|
202
|
-
break
|
|
203
|
-
}
|
|
204
|
-
case 'aa05': {
|
|
205
|
-
// Ice size report. The payload encoding is deliberately NOT decoded here:
|
|
206
|
-
// sizeCodes runs small=0x03..large=0x01 while the OpenAPI path sends
|
|
207
|
-
// workMode un-inverted, and which is authoritative is unresolved (#1329).
|
|
208
|
-
// Claiming a mapping here could silently swap sizes, so this case exists
|
|
209
|
-
// only to stop a routine status report being logged as a new scene code.
|
|
210
|
-
break
|
|
211
|
-
}
|
|
212
|
-
default:
|
|
213
|
-
this.accessory.logWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
214
|
-
break
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
}
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { base64ToHex, getTwoItemPosition, hexToTwoItems, parseError } from '../utils/functions.js'
|
|
2
|
-
import platformLang from '../utils/lang-en.js'
|
|
3
|
-
|
|
4
|
-
export default class {
|
|
5
|
-
constructor(platform, accessory) {
|
|
6
|
-
// Set up variables from the platform
|
|
7
|
-
this.hapChar = platform.api.hap.Characteristic
|
|
8
|
-
this.hapErr = platform.api.hap.HapStatusError
|
|
9
|
-
this.hapServ = platform.api.hap.Service
|
|
10
|
-
this.platform = platform
|
|
11
|
-
|
|
12
|
-
// Set up variables from the accessory
|
|
13
|
-
this.accessory = accessory
|
|
14
|
-
|
|
15
|
-
// Ice size codes: small=1, medium=2, large=3 (matching OpenAPI workMode values)
|
|
16
|
-
this.sizeCodes = {
|
|
17
|
-
1: 'MwUDAAAAAAAAAAAAAAAAAAAAADU=', // small
|
|
18
|
-
2: 'MwUCAAAAAAAAAAAAAAAAAAAAADQ=', // medium
|
|
19
|
-
3: 'MwUBAAAAAAAAAAAAAAAAAAAAADc=', // large
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
this.sizeLabels = {
|
|
23
|
-
1: 'small',
|
|
24
|
-
2: 'medium',
|
|
25
|
-
3: 'large',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Remove old switch service if migrating
|
|
29
|
-
if (this.accessory.getService(this.hapServ.Switch)) {
|
|
30
|
-
this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Add the fan service for ice size control
|
|
34
|
-
this.service = this.accessory.getService(this.hapServ.Fanv2)
|
|
35
|
-
|| this.accessory.addService(this.hapServ.Fanv2)
|
|
36
|
-
|
|
37
|
-
// Add the set handler to the on/off characteristic
|
|
38
|
-
this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
|
|
39
|
-
await this.internalStateUpdate(value)
|
|
40
|
-
})
|
|
41
|
-
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
42
|
-
|
|
43
|
-
// Add the set handler to the rotation speed characteristic (ice size)
|
|
44
|
-
this.service
|
|
45
|
-
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
46
|
-
.setProps({
|
|
47
|
-
maxValue: 3,
|
|
48
|
-
minStep: 1,
|
|
49
|
-
minValue: 0,
|
|
50
|
-
unit: 'unitless',
|
|
51
|
-
})
|
|
52
|
-
.onSet(async value => this.internalSizeUpdate(value))
|
|
53
|
-
this.cacheSize = this.service.getCharacteristic(this.hapChar.RotationSpeed).value || 2
|
|
54
|
-
|
|
55
|
-
// Keep the characteristic in step with the cache. Without this an accessory
|
|
56
|
-
// that has never had a size set reports Active=1 with RotationSpeed=0, which
|
|
57
|
-
// is contradictory and which the Home app renders as "0".
|
|
58
|
-
if (this.service.getCharacteristic(this.hapChar.RotationSpeed).value !== this.cacheSize) {
|
|
59
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Output the customised options to the log
|
|
63
|
-
const opts = JSON.stringify({})
|
|
64
|
-
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async internalStateUpdate(value) {
|
|
68
|
-
try {
|
|
69
|
-
const newState = value ? 'on' : 'off'
|
|
70
|
-
|
|
71
|
-
// Don't continue if the new value is the same as before
|
|
72
|
-
if (newState === this.cacheState) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (value) {
|
|
77
|
-
// Turn on with current cached ice size
|
|
78
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
79
|
-
cmd: 'ptReal',
|
|
80
|
-
value: this.sizeCodes[this.cacheSize],
|
|
81
|
-
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
82
|
-
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: this.cacheSize, modeValue: 0 } }
|
|
83
|
-
: undefined,
|
|
84
|
-
})
|
|
85
|
-
} else {
|
|
86
|
-
// Turn off
|
|
87
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
88
|
-
cmd: 'ptReal',
|
|
89
|
-
value: 'MxkAAAAAAAAAAAAAAAAAAAAAACo=',
|
|
90
|
-
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
91
|
-
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: 0, modeValue: 0 } }
|
|
92
|
-
: undefined,
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Cache the new state and log if appropriate
|
|
97
|
-
if (this.cacheState !== newState) {
|
|
98
|
-
this.cacheState = newState
|
|
99
|
-
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
100
|
-
}
|
|
101
|
-
} catch (err) {
|
|
102
|
-
// Catch any errors during the process
|
|
103
|
-
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
104
|
-
|
|
105
|
-
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
106
|
-
setTimeout(() => {
|
|
107
|
-
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
108
|
-
}, 2000)
|
|
109
|
-
throw new this.hapErr(-70402)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async internalSizeUpdate(value) {
|
|
114
|
-
try {
|
|
115
|
-
// 0 is not a valid ice size. Put the slider back, otherwise HomeKit keeps 0
|
|
116
|
-
// while the device carries on at cacheSize - and that 0 is what gets written
|
|
117
|
-
// to the accessory cache, so it returns on the next restart. Deferred for the
|
|
118
|
-
// same reason as the error reverts below: an immediate update would be
|
|
119
|
-
// overwritten by the value HomeKit is currently writing.
|
|
120
|
-
if (value === 0) {
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
123
|
-
}, 1000)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Don't continue if the new value is the same as before
|
|
128
|
-
if (value === this.cacheSize) {
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
133
|
-
cmd: 'ptReal',
|
|
134
|
-
value: this.sizeCodes[value],
|
|
135
|
-
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
136
|
-
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: value, modeValue: 0 } }
|
|
137
|
-
: undefined,
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// Cache the new state and log if appropriate
|
|
141
|
-
this.cacheSize = value
|
|
142
|
-
this.cacheState = 'on'
|
|
143
|
-
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
144
|
-
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[value]}]`)
|
|
145
|
-
} catch (err) {
|
|
146
|
-
// Catch any errors during the process
|
|
147
|
-
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
148
|
-
|
|
149
|
-
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
150
|
-
setTimeout(() => {
|
|
151
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
152
|
-
}, 2000)
|
|
153
|
-
throw new this.hapErr(-70402)
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
externalUpdate(params) {
|
|
158
|
-
// Handle OpenAPI workMode
|
|
159
|
-
if (params.workMode) {
|
|
160
|
-
const mode = params.workMode.workMode
|
|
161
|
-
if (mode > 0 && mode <= 3) {
|
|
162
|
-
if (this.cacheState !== 'on') {
|
|
163
|
-
this.cacheState = 'on'
|
|
164
|
-
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
165
|
-
this.accessory.log(`${platformLang.curState} [on]`)
|
|
166
|
-
}
|
|
167
|
-
if (this.cacheSize !== mode) {
|
|
168
|
-
this.cacheSize = mode
|
|
169
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
170
|
-
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[this.cacheSize]}]`)
|
|
171
|
-
}
|
|
172
|
-
} else if (mode === 0) {
|
|
173
|
-
if (this.cacheState !== 'off') {
|
|
174
|
-
this.cacheState = 'off'
|
|
175
|
-
this.service.updateCharacteristic(this.hapChar.Active, 0)
|
|
176
|
-
this.accessory.log(`${platformLang.curState} [off]`)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Check for some other scene/mode change
|
|
182
|
-
(params.commands || []).forEach((command) => {
|
|
183
|
-
const hexString = base64ToHex(command)
|
|
184
|
-
const hexParts = hexToTwoItems(hexString)
|
|
185
|
-
|
|
186
|
-
// Return now if not a device query update code
|
|
187
|
-
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
|
|
192
|
-
|
|
193
|
-
switch (deviceFunction) {
|
|
194
|
-
case 'aa19': {
|
|
195
|
-
// On/Off
|
|
196
|
-
const newState = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
197
|
-
if (this.cacheState !== newState) {
|
|
198
|
-
this.cacheState = newState
|
|
199
|
-
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
200
|
-
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
201
|
-
}
|
|
202
|
-
break
|
|
203
|
-
}
|
|
204
|
-
case 'aa05': {
|
|
205
|
-
// Ice size report. The payload encoding is deliberately NOT decoded here:
|
|
206
|
-
// sizeCodes runs small=0x03..large=0x01 while the OpenAPI path sends
|
|
207
|
-
// workMode un-inverted, and which is authoritative is unresolved (#1329).
|
|
208
|
-
// Claiming a mapping here could silently swap sizes, so this case exists
|
|
209
|
-
// only to stop a routine status report being logged as a new scene code.
|
|
210
|
-
break
|
|
211
|
-
}
|
|
212
|
-
default:
|
|
213
|
-
this.accessory.logWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
214
|
-
break
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
}
|