@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/config.schema.json +1 -1
  3. package/lib/connection/ble.js +26 -1
  4. package/lib/connection/openapi.js +18 -0
  5. package/lib/device/appliance-basic.js +377 -0
  6. package/lib/device/dehumidifier-H7150.js +9 -3
  7. package/lib/device/diffuser-H7161.js +7 -1
  8. package/lib/device/fan-H1370.js +7 -1
  9. package/lib/device/fan-H7100.js +36 -4
  10. package/lib/device/fan-H7105.js +7 -1
  11. package/lib/device/fan-H7107.js +7 -1
  12. package/lib/device/heater1a.js +52 -12
  13. package/lib/device/heater1b.js +14 -3
  14. package/lib/device/heater2.js +7 -1
  15. package/lib/device/humidifier-H7140.js +7 -1
  16. package/lib/device/humidifier-H7141.js +7 -1
  17. package/lib/device/humidifier-H7142.js +7 -1
  18. package/lib/device/humidifier-H7143.js +7 -1
  19. package/lib/device/humidifier-H7160.js +7 -1
  20. package/lib/device/ice-maker-H7172.js +19 -7
  21. package/lib/device/index.js +44 -51
  22. package/lib/device/kettle.js +7 -1
  23. package/lib/device/outlet-single.js +7 -1
  24. package/lib/device/purifier-H7120.js +14 -25
  25. package/lib/device/purifier-H7122.js +7 -1
  26. package/lib/device/purifier-H7123.js +9 -3
  27. package/lib/device/purifier-H7126.js +7 -1
  28. package/lib/device/{sensor-button.js → sensor-basic.js} +14 -2
  29. package/lib/device/sensor-presence.js +7 -1
  30. package/lib/device/sensor-thermo4.js +13 -2
  31. package/lib/device/switch-double.js +34 -15
  32. package/lib/device/switch-triple.js +36 -17
  33. package/lib/device/template.js +15 -2
  34. package/lib/platform.js +83 -799
  35. package/lib/utils/capabilities.js +220 -0
  36. package/lib/utils/command-builder.js +6 -4
  37. package/lib/utils/config-parser.js +280 -0
  38. package/lib/utils/constants.js +120 -8
  39. package/lib/utils/decode.js +4 -1
  40. package/lib/utils/device-capabilities.js +77 -3
  41. package/lib/utils/device-context.js +179 -0
  42. package/lib/utils/device-merge.js +251 -0
  43. package/lib/utils/dispatch.js +159 -0
  44. package/lib/utils/functions.js +9 -0
  45. package/lib/utils/lang-en.js +0 -2
  46. package/lib/utils/report-unknown.js +156 -0
  47. package/package.json +1 -1
  48. package/lib/device/dehumidifier-H7151.js +0 -189
  49. package/lib/device/dehumidifier-H7152.js +0 -180
  50. package/lib/device/diffuser-H7162.js +0 -123
  51. package/lib/device/fan-H1310.js +0 -284
  52. package/lib/device/fan-H7101.js +0 -340
  53. package/lib/device/fan-H7102.js +0 -284
  54. package/lib/device/fan-H7106.js +0 -284
  55. package/lib/device/fan-H7111.js +0 -345
  56. package/lib/device/humidifier-H7145.js +0 -187
  57. package/lib/device/humidifier-H7147.js +0 -187
  58. package/lib/device/humidifier-H7148.js +0 -187
  59. package/lib/device/humidifier-H7149.js +0 -187
  60. package/lib/device/humidifier-H714E.js +0 -187
  61. package/lib/device/ice-maker-H717D.js +0 -218
  62. package/lib/device/ice-maker-H8120.js +0 -218
  63. package/lib/device/outlet-double.js +0 -188
  64. package/lib/device/outlet-triple.js +0 -160
  65. package/lib/device/purifier-H7121.js +0 -406
  66. package/lib/device/purifier-H7124.js +0 -504
  67. package/lib/device/purifier-H7127.js +0 -359
  68. package/lib/device/purifier-H7128.js +0 -359
  69. package/lib/device/purifier-H7129.js +0 -359
  70. package/lib/device/purifier-H712C.js +0 -359
  71. package/lib/device/sensor-contact.js +0 -22
  72. package/lib/utils/ble-timeouts.test.js +0 -44
  73. package/lib/utils/functions.test.js +0 -292
@@ -1,504 +0,0 @@
1
- import {
2
- base64ToHex,
3
- getTwoItemPosition,
4
- hexToBase64,
5
- hexToTwoItems,
6
- parseError,
7
- statusToActionCode,
8
- } from '../utils/functions.js'
9
- import platformLang from '../utils/lang-en.js'
10
-
11
- /*
12
- H7123
13
- {
14
- "mode": {
15
- "options": [
16
- {
17
- "name": "Low",
18
- "value": 1
19
- },
20
- {
21
- "name": "Medium",
22
- "value": 2
23
- },
24
- {
25
- "name": "High",
26
- "value": 3
27
- },
28
- {
29
- "name": "Auto mode",
30
- "value": 4
31
- },
32
- {
33
- "name": "Sleep mode",
34
- "value": 5
35
- }
36
- ]
37
- }
38
- }
39
- */
40
-
41
- export default class {
42
- constructor(platform, accessory) {
43
- // Set up variables from the platform
44
- this.cusChar = platform.cusChar
45
- this.hapChar = platform.api.hap.Characteristic
46
- this.hapErr = platform.api.hap.HapStatusError
47
- this.hapServ = platform.api.hap.Service
48
- this.platform = platform
49
-
50
- // Set up variables from the accessory
51
- this.accessory = accessory
52
-
53
- // Speed codes
54
- this.value2Code = {
55
- 1: 'OgUFAAAAAAAAAAAAAAAAAAAAADo=', // sleep
56
- 2: 'OgUBAQAAAAAAAAAAAAAAAAAAAD8=', // low
57
- 3: 'OgUBAgAAAAAAAAAAAAAAAAAAADw=', // med
58
- 4: 'OgUBAwAAAAAAAAAAAAAAAAAAAD0=', // high
59
- 5: 'OgUDAAAAAAAAAAAAAAAAAAAAADw=', // auto
60
- }
61
-
62
- this.value2Label = {
63
- 0: 'off',
64
- 1: 'sleep',
65
- 2: 'low',
66
- 3: 'medium',
67
- 4: 'high',
68
- 5: 'auto',
69
- }
70
-
71
- this.night2Code = {
72
- on: 'MxgBMgAAAAAAAAAAAAAAAAAAABg=',
73
- off: 'MxgAMgAAAAAAAAAAAAAAAAAAABk=',
74
- }
75
-
76
- this.airValue2Label = {
77
- 1: 'excellent',
78
- 2: 'good',
79
- 3: 'moderate',
80
- 4: 'poor',
81
- }
82
-
83
- // Add the purifier service if it doesn't already exist
84
- this.service = this.accessory.getService(this.hapServ.AirPurifier)
85
- || this.accessory.addService(this.hapServ.AirPurifier)
86
-
87
- // Add the air quality service if it doesn't already exist
88
- this.airService = this.accessory.getService(this.hapServ.AirQualitySensor)
89
- || this.accessory.addService(this.hapServ.AirQualitySensor)
90
-
91
- if (this.airService.testCharacteristic(this.hapChar.PM2_5Density)) {
92
- this.airService.removeCharacteristic(this.airService.getCharacteristic(this.hapChar.PM2_5Density))
93
- }
94
-
95
- this.cacheAir = this.airService.getCharacteristic(this.hapChar.AirQuality).value
96
-
97
- // Add the set handler to the switch on/off characteristic
98
- this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
99
- await this.internalStateUpdate(value)
100
- })
101
- this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
102
-
103
- // Add options to the purifier target state characteristic
104
- this.service
105
- .getCharacteristic(this.hapChar.TargetAirPurifierState)
106
- .updateValue(1)
107
- .setProps({
108
- minValue: 1,
109
- maxValue: 1,
110
- validValues: [1],
111
- })
112
-
113
- // Add the set handler to the fan rotation speed characteristic
114
- this.service
115
- .getCharacteristic(this.hapChar.RotationSpeed)
116
- .setProps({
117
- minStep: 20,
118
- validValues: [0, 20, 40, 60, 80, 100],
119
- })
120
- .onSet(async value => this.internalSpeedUpdate(value))
121
- this.cacheMode = this.service.getCharacteristic(this.hapChar.RotationSpeed).value / 20
122
-
123
- // Add the set handler to the lock controls characteristic
124
- this.service.getCharacteristic(this.hapChar.LockPhysicalControls).onSet(async (value) => {
125
- await this.internalLockUpdate(value)
126
- })
127
- this.cacheLock = this.service.getCharacteristic(this.hapChar.LockPhysicalControls).value === 0 ? 'off' : 'on'
128
-
129
- // Add night light Eve characteristic if it doesn't exist already
130
- if (!this.service.testCharacteristic(this.cusChar.NightLight)) {
131
- this.service.addCharacteristic(this.cusChar.NightLight)
132
- }
133
-
134
- // Add the set handler to the custom night light characteristic
135
- this.service.getCharacteristic(this.cusChar.NightLight).onSet(async (value) => {
136
- await this.internalNightLightUpdate(value)
137
- })
138
- this.cacheLight = this.service.getCharacteristic(this.cusChar.NightLight).value ? 'on' : 'off'
139
-
140
- // Add display light Eve characteristic if it doesn't exist already
141
- if (!this.service.testCharacteristic(this.cusChar.DisplayLight)) {
142
- this.service.addCharacteristic(this.cusChar.DisplayLight)
143
- }
144
-
145
- // Add the set handler to the custom display light characteristic
146
- this.service.getCharacteristic(this.cusChar.DisplayLight).onSet(async (value) => {
147
- await this.internalDisplayLightUpdate(value)
148
- })
149
- this.cacheDisplay = this.service.getCharacteristic(this.cusChar.DisplayLight).value
150
- ? 'on'
151
- : 'off'
152
-
153
- // Output the customised options to the log
154
- const opts = JSON.stringify({})
155
- platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
156
- }
157
-
158
- async internalStateUpdate(value) {
159
- try {
160
- const newValue = value === 1 ? 'on' : 'off'
161
-
162
- // Don't continue if the new value is the same as before
163
- if (this.cacheState === newValue) {
164
- return
165
- }
166
-
167
- // Send the request to the platform sender function
168
- await this.platform.sendDeviceUpdate(this.accessory, {
169
- cmd: 'statePuri',
170
- value: value ? 1 : 0,
171
- })
172
-
173
- // Update the current state characteristic
174
- this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
175
-
176
- // Cache the new state and log if appropriate
177
- this.cacheState = newValue
178
- this.accessory.log(`${platformLang.curState} [${newValue}]`)
179
- } catch (err) {
180
- // Catch any errors during the process
181
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
182
-
183
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
184
- setTimeout(() => {
185
- this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
186
- }, 2000)
187
- throw new this.hapErr(-70402)
188
- }
189
- }
190
-
191
- async internalSpeedUpdate(value) {
192
- try {
193
- // Don't continue if the speed is 0
194
- if (value === 0) {
195
- return
196
- }
197
-
198
- // Get the single Govee value {1, 2, 3, 4}
199
- const newValueKey = value / 20
200
-
201
- // Don't continue if the speed value won't have effect
202
- if (!newValueKey || newValueKey === this.cacheMode) {
203
- return
204
- }
205
-
206
- // Send the request to the platform sender function
207
- await this.platform.sendDeviceUpdate(this.accessory, {
208
- cmd: 'ptReal',
209
- value: this.value2Code[newValueKey],
210
- openApi: this.accessory.context.openApiCapabilities?.workMode
211
- ? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: 1, modeValue: newValueKey } }
212
- : undefined,
213
- })
214
-
215
- // Cache the new state and log if appropriate
216
- this.cacheMode = newValueKey
217
- this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
218
- } catch (err) {
219
- // Catch any errors during the process
220
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
221
-
222
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
223
- setTimeout(() => {
224
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
225
- }, 2000)
226
- throw new this.hapErr(-70402)
227
- }
228
- }
229
-
230
- async internalNightLightUpdate(value) {
231
- try {
232
- const newValue = value ? 'on' : 'off'
233
-
234
- // Don't continue if the new value is the same as before
235
- if (this.cacheLight === newValue) {
236
- return
237
- }
238
-
239
- // Send the request to the platform sender function
240
- await this.platform.sendDeviceUpdate(this.accessory, {
241
- cmd: 'ptReal',
242
- value: this.night2Code[newValue],
243
- openApi: this.accessory.context.openApiCapabilities?.nightlightToggle
244
- ? { instance: 'nightlightToggle', capabilityType: 'devices.capabilities.toggle', value: newValue === 'on' ? 1 : 0 }
245
- : undefined,
246
- })
247
-
248
- // Cache the new state and log if appropriate
249
- this.cacheLight = newValue
250
- this.accessory.log(`${platformLang.curLight} [${newValue}]`)
251
- } catch (err) {
252
- // Catch any errors during the process
253
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
254
-
255
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
256
- setTimeout(() => {
257
- this.service.updateCharacteristic(this.cusChar.NightLight, this.cacheLight === 'on')
258
- }, 2000)
259
- throw new this.hapErr(-70402)
260
- }
261
- }
262
-
263
- async internalLockUpdate(value) {
264
- try {
265
- const newValue = value === 1 ? 'on' : 'off'
266
-
267
- // Don't continue if the new value is the same as before
268
- if (this.cacheLock === newValue) {
269
- return
270
- }
271
-
272
- // Send the request to the platform sender function
273
- await this.platform.sendDeviceUpdate(this.accessory, {
274
- cmd: 'ptReal',
275
- value: value ? 'MxABAAAAAAAAAAAAAAAAAAAAACI=' : 'MxAAAAAAAAAAAAAAAAAAAAAAACM=',
276
- openApi: this.accessory.context.openApiCapabilities?.lockToggle
277
- ? { instance: 'lockToggle', capabilityType: 'devices.capabilities.toggle', value: value ? 1 : 0 }
278
- : undefined,
279
- })
280
-
281
- // Cache the new state and log if appropriate
282
- this.cacheLock = newValue
283
- this.accessory.log(`${platformLang.curLock} [${newValue}]`)
284
- } catch (err) {
285
- // Catch any errors during the process
286
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
287
-
288
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
289
- setTimeout(() => {
290
- this.service.updateCharacteristic(
291
- this.hapChar.LockPhysicalControls,
292
- this.cacheLock === 'on' ? 1 : 0,
293
- )
294
- }, 2000)
295
- throw new this.hapErr(-70402)
296
- }
297
- }
298
-
299
- async internalDisplayLightUpdate(value) {
300
- try {
301
- const newValue = value ? 'on' : 'off'
302
-
303
- // Don't continue if the new value is the same as before
304
- if (this.cacheDisplay === newValue) {
305
- return
306
- }
307
-
308
- // Generate the code to send
309
- let codeToSend
310
- if (value) {
311
- codeToSend = this.accessory.context.cacheDisplayCode
312
- ? hexToBase64(statusToActionCode(this.accessory.context.cacheDisplayCode))
313
- : 'MxYBAAAAAAAAAAAAAAAAAAAAACQ='
314
- } else {
315
- codeToSend = 'MxYAAAAAAAAAAAAAAAAAAAAAACU='
316
- }
317
-
318
- // Send the request to the platform sender function
319
- await this.platform.sendDeviceUpdate(this.accessory, {
320
- cmd: 'ptReal',
321
- value: codeToSend,
322
- openApi: this.accessory.context.openApiCapabilities?.displayToggle
323
- ? { instance: 'displayToggle', capabilityType: 'devices.capabilities.toggle', value: value ? 1 : 0 }
324
- : undefined,
325
- })
326
-
327
- // Cache the new state and log if appropriate
328
- this.cacheDisplay = newValue
329
- this.accessory.log(`${platformLang.curDisplay} [${newValue}]`)
330
- } catch (err) {
331
- // Catch any errors during the process
332
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
333
-
334
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
335
- setTimeout(() => {
336
- this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
337
- }, 2000)
338
- throw new this.hapErr(-70402)
339
- }
340
- }
341
-
342
- externalUpdate(params) {
343
- // Check for an ON/OFF change
344
- if (params.state && params.state !== this.cacheState) {
345
- this.cacheState = params.state
346
- this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on')
347
- this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, this.cacheState === 'on' ? 2 : 0)
348
-
349
- // Log the change
350
- this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
351
- }
352
-
353
- // Handle OpenAPI workMode (speed/mode)
354
- if (params.workMode) {
355
- const speed = params.workMode.modeValue
356
- if (speed >= 1 && speed <= 5) {
357
- if (speed !== this.cacheMode) {
358
- this.cacheMode = speed
359
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
360
- this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
361
- }
362
- }
363
- }
364
-
365
- // Handle OpenAPI toggles
366
- if (params.toggles) {
367
- if (params.toggles.lockToggle !== undefined) {
368
- const newLock = params.toggles.lockToggle ? 'on' : 'off'
369
- if (newLock !== this.cacheLock) {
370
- this.cacheLock = newLock
371
- this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
372
- this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
373
- }
374
- }
375
- if (params.toggles.nightlightToggle !== undefined) {
376
- const newLight = params.toggles.nightlightToggle ? 'on' : 'off'
377
- if (this.cacheLight !== newLight) {
378
- this.cacheLight = newLight
379
- this.service.updateCharacteristic(this.cusChar.NightLight, this.cacheLight === 'on')
380
- this.accessory.log(`${platformLang.curLight} [${this.cacheLight}]`)
381
- }
382
- }
383
- if (params.toggles.displayToggle !== undefined) {
384
- const newDisplay = params.toggles.displayToggle ? 'on' : 'off'
385
- if (newDisplay !== this.cacheDisplay) {
386
- this.cacheDisplay = newDisplay
387
- this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
388
- this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
389
- }
390
- }
391
- }
392
-
393
- (params.commands || []).forEach((command) => {
394
- const hexString = base64ToHex(command)
395
- const hexParts = hexToTwoItems(hexString)
396
-
397
- const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
398
-
399
- switch (deviceFunction) {
400
- case 'aa05': // speed
401
- case '3a05': { // speed
402
- const newSpeedCode = `${getTwoItemPosition(hexParts, 3)}${getTwoItemPosition(hexParts, 4)}`
403
-
404
- // Different behaviour for custom speed
405
- if (newSpeedCode === '0202') {
406
- this.accessory.log(`${platformLang.curMode} [custom]`)
407
- return
408
- }
409
-
410
- let newMode
411
-
412
- switch (newSpeedCode) {
413
- case '0500': {
414
- // Sleep
415
- newMode = 1
416
- break
417
- }
418
- case '0101': {
419
- // Low
420
- newMode = 2
421
- break
422
- }
423
- case '0102': {
424
- // Medium
425
- newMode = 3
426
- break
427
- }
428
- case '0103': {
429
- // High
430
- newMode = 4
431
- break
432
- }
433
- case '0300': {
434
- // Auto
435
- newMode = 5
436
- break
437
- }
438
- }
439
-
440
- if (newMode && newMode !== this.cacheMode) {
441
- this.cacheMode = newMode
442
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheMode * 20)
443
- this.accessory.log(`${platformLang.curMode} [${this.value2Label[this.cacheMode]}]`)
444
- }
445
- break
446
- }
447
- case 'aa10': { // lock
448
- const newLock = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
449
- if (newLock !== this.cacheLock) {
450
- this.cacheLock = newLock
451
- this.service.updateCharacteristic(this.hapChar.LockPhysicalControls, this.cacheLock === 'on' ? 1 : 0)
452
- this.accessory.log(`${platformLang.curLock} [${this.cacheLock}]`)
453
- }
454
- break
455
- }
456
- case 'aa16': { // display light
457
- const newDisplay = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
458
- if (newDisplay === 'on') {
459
- this.accessory.context.cacheDisplayCode = hexString
460
- }
461
- if (newDisplay !== this.cacheDisplay) {
462
- this.cacheDisplay = newDisplay
463
- this.service.updateCharacteristic(this.cusChar.DisplayLight, this.cacheDisplay === 'on')
464
-
465
- // Log the change
466
- this.accessory.log(`${platformLang.curDisplay} [${this.cacheDisplay}]`)
467
- }
468
- break
469
- }
470
- case 'aa19': {
471
- // Check air quality reading (i.e. 1=green, 2=blue, 3=yellow, 4=red)
472
- // Cache will be in {1, 2, 3, 5} which relates to Govee {1, 2, 3, 4}
473
- let newQual = Number.parseInt(getTwoItemPosition(hexParts, 5), 16)
474
- if (newQual === 4) {
475
- newQual = 5
476
- }
477
-
478
- if (newQual !== this.cacheAir) {
479
- this.airService.updateCharacteristic(this.hapChar.AirQuality, newQual)
480
- this.accessory.log(`${platformLang.curAirQual} [${this.airValue2Label[newQual]}]`)
481
- }
482
- break
483
- }
484
- case 'aa11': // timer
485
- case 'aa13': // scheduling
486
- case '3310': // lock (same command for on and off)
487
- case '3311': // timer
488
- case '3313': // scheduling
489
- case '3316': { // display light
490
- break
491
- }
492
- default:
493
- this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
494
- break
495
- }
496
- })
497
- }
498
- }
499
-
500
- // GOOD
501
- // [28/12/2022, 00:44:48] [Govee] [Pet Air Purifier] new scene code: [qhkAKQJdAAAAAAAAAAAAAAAAAMU=] [aa 19 00 29 02 5d 00000000000000000000000000c5].
502
-
503
- // EXC
504
- // [28/12/2022, 01:12:05] [Govee] [Pet Air Purifier] new scene code: [qhkAHQFdAAAAAAAAAAAAAAAAAPI=] [aa 19 00 1d 01 5d 00000000000000000000000000f2].