@switchbot/homebridge-switchbot 4.0.1-beta.5 → 4.1.0

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@switchbot/homebridge-switchbot",
3
3
  "displayName": "SwitchBot",
4
4
  "type": "module",
5
- "version": "4.0.1-beta.5",
5
+ "version": "4.1.0",
6
6
  "description": "The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.",
7
7
  "author": "SwitchBot <support@wondertechlabs.com> (https://github.com/SwitchBot)",
8
8
  "contributors": [
@@ -81,7 +81,7 @@
81
81
  "async-mqtt": "^2.6.3",
82
82
  "fakegato-history": "^0.6.5",
83
83
  "homebridge-lib": "^7.0.9",
84
- "node-switchbot": "^3.1.2-beta.5",
84
+ "node-switchbot": "^3.2.1",
85
85
  "rxjs": "^7.8.1"
86
86
  },
87
87
  "devDependencies": {
@@ -90,7 +90,7 @@
90
90
  "@types/debug": "^4.1.12",
91
91
  "@types/fs-extra": "^11.0.4",
92
92
  "@types/mdast": "^4.0.4",
93
- "@types/node": "^22.7.7",
93
+ "@types/node": "^22.7.8",
94
94
  "@types/semver": "^7.5.8",
95
95
  "@types/source-map-support": "^0.5.10",
96
96
  "@vitest/coverage-v8": "^2.1.3",
@@ -0,0 +1,417 @@
1
+ /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved.
2
+ *
3
+ * MeterPro.ts: @switchbot/homebridge-switchbot.
4
+ */
5
+ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'
6
+ import type { device, meterProServiceData, meterProStatus, meterProWebhookContext } from 'node-switchbot'
7
+
8
+ import type { SwitchBotPlatform } from '../platform.js'
9
+ import type { devicesConfig, meterConfig } from '../settings.js'
10
+
11
+ import { Units } from 'homebridge'
12
+ /*
13
+ * For Testing Locally:
14
+ * import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js';
15
+ */
16
+ import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'
17
+ import { interval, skipWhile, Subject } from 'rxjs'
18
+
19
+ import { convertUnits, formatDeviceIdAsMac, validHumidity } from '../utils.js'
20
+ import { deviceBase } from './device.js'
21
+
22
+ /**
23
+ * Platform Accessory
24
+ * An instance of this class is created for each accessory your platform registers
25
+ * Each accessory may expose multiple services of different service types.
26
+ */
27
+ export class MeterPro extends deviceBase {
28
+ // Services
29
+ private Battery: {
30
+ Name: CharacteristicValue
31
+ Service: Service
32
+ BatteryLevel: CharacteristicValue
33
+ StatusLowBattery: CharacteristicValue
34
+ }
35
+
36
+ private HumiditySensor?: {
37
+ Name: CharacteristicValue
38
+ Service: Service
39
+ CurrentRelativeHumidity: CharacteristicValue
40
+ }
41
+
42
+ private TemperatureSensor?: {
43
+ Name: CharacteristicValue
44
+ Service: Service
45
+ CurrentTemperature: CharacteristicValue
46
+ }
47
+
48
+ // OpenAPI
49
+ deviceStatus!: meterProStatus
50
+
51
+ // Webhook
52
+ webhookContext!: meterProWebhookContext
53
+
54
+ // BLE
55
+ serviceData!: meterProServiceData
56
+
57
+ // Updates
58
+ meterUpdateInProgress!: boolean
59
+ doMeterUpdate: Subject<void>
60
+
61
+ constructor(
62
+ readonly platform: SwitchBotPlatform,
63
+ accessory: PlatformAccessory,
64
+ device: device & devicesConfig,
65
+ ) {
66
+ super(platform, accessory, device)
67
+ // Set category
68
+ accessory.category = this.hap.Categories.SENSOR
69
+
70
+ // this is subject we use to track when we need to POST changes to the SwitchBot API
71
+ this.doMeterUpdate = new Subject()
72
+ this.meterUpdateInProgress = false
73
+
74
+ // Initialize Battery Service
75
+ accessory.context.Battery = accessory.context.Battery ?? {}
76
+ this.Battery = {
77
+ Name: `${accessory.displayName} Battery`,
78
+ Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service,
79
+ BatteryLevel: accessory.context.BatteryLevel ?? 100,
80
+ StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL,
81
+ }
82
+ accessory.context.Battery = this.Battery as object
83
+
84
+ // Initialize Battery Characteristics
85
+ this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => {
86
+ return this.Battery.BatteryLevel
87
+ })
88
+
89
+ this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => {
90
+ return this.Battery.StatusLowBattery
91
+ })
92
+
93
+ // Initialize Temperature Sensor Service
94
+ if ((device as meterConfig).hide_temperature) {
95
+ if (this.TemperatureSensor) {
96
+ this.debugLog('Removing Temperature Sensor Service')
97
+ this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service
98
+ accessory.removeService(this.TemperatureSensor.Service)
99
+ }
100
+ } else {
101
+ accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {}
102
+ this.TemperatureSensor = {
103
+ Name: `${accessory.displayName} Temperature Sensor`,
104
+ Service: accessory.getService(this.hap.Service.TemperatureSensor) ?? this.accessory.addService(this.hap.Service.TemperatureSensor) as Service,
105
+ CurrentTemperature: accessory.context.CurrentTemperature ?? 30,
106
+ }
107
+ accessory.context.TemperatureSensor = this.TemperatureSensor as object
108
+
109
+ // Initialize Temperature Sensor Characteristics
110
+ this.TemperatureSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentTemperature).setProps({
111
+ unit: Units.CELSIUS,
112
+ validValueRanges: [-273.15, 100],
113
+ minValue: -273.15,
114
+ maxValue: 100,
115
+ minStep: 0.1,
116
+ }).onGet(() => {
117
+ return this.TemperatureSensor!.CurrentTemperature!
118
+ })
119
+ }
120
+ // Initialize Humidity Sensor Service
121
+ if ((device as meterConfig).hide_humidity) {
122
+ if (this.HumiditySensor) {
123
+ this.debugLog('Removing Humidity Sensor Service')
124
+ this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service
125
+ accessory.removeService(this.HumiditySensor.Service)
126
+ }
127
+ } else {
128
+ accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {}
129
+ this.HumiditySensor = {
130
+ Name: `${accessory.displayName} Humidity Sensor`,
131
+ Service: accessory.getService(this.hap.Service.HumiditySensor) ?? this.accessory.addService(this.hap.Service.HumiditySensor) as Service,
132
+ CurrentRelativeHumidity: accessory.context.CurrentRelativeHumidity ?? 50,
133
+ }
134
+ accessory.context.HumiditySensor = this.HumiditySensor as object
135
+
136
+ // Initialize Humidity Sensor Characteristics
137
+ this.HumiditySensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name).getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity).setProps({
138
+ minStep: 0.1,
139
+ }).onGet(() => {
140
+ return this.HumiditySensor!.CurrentRelativeHumidity!
141
+ })
142
+ }
143
+
144
+ // Retrieve initial values and updateHomekit
145
+ try {
146
+ this.debugLog('Retrieve initial values and update Homekit')
147
+ this.refreshStatus()
148
+ } catch (e: any) {
149
+ this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e.message ?? e}`)
150
+ }
151
+
152
+ // regisiter webhook event handler if enabled
153
+ try {
154
+ this.debugLog('Registering Webhook Event Handler')
155
+ this.registerWebhook()
156
+ } catch (e: any) {
157
+ this.errorLog(`failed to registerWebhook, Error: ${e.message ?? e}`)
158
+ }
159
+
160
+ // regisiter platform BLE event handler if enabled
161
+ try {
162
+ this.debugLog('Registering Platform BLE Event Handler')
163
+ this.registerPlatformBLE()
164
+ } catch (e: any) {
165
+ this.errorLog(`failed to registerPlatformBLE, Error: ${e.message ?? e}`)
166
+ }
167
+
168
+ // Start an update interval
169
+ interval(this.deviceRefreshRate * 1000)
170
+ .pipe(skipWhile(() => this.meterUpdateInProgress))
171
+ .subscribe(async () => {
172
+ await this.refreshStatus()
173
+ })
174
+ }
175
+
176
+ async BLEparseStatus(): Promise<void> {
177
+ await this.debugLog('BLEparseStatus')
178
+ await this.debugLog(`(temperature, humidity) = BLE:(${this.serviceData.celsius}, ${this.serviceData.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`)
179
+
180
+ // CurrentRelativeHumidity
181
+ if (!(this.device as meterConfig).hide_humidity && this.HumiditySensor?.Service) {
182
+ this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100)
183
+ await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`)
184
+ }
185
+ // Current Temperature
186
+ if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
187
+ const CELSIUS = this.serviceData.celsius < 0 ? 0 : this.serviceData.celsius > 100 ? 100 : this.serviceData.celsius
188
+ this.TemperatureSensor.CurrentTemperature = CELSIUS
189
+ await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
190
+ }
191
+ // BatteryLevel
192
+ this.Battery.BatteryLevel = this.serviceData.battery
193
+ await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`)
194
+ // StatusLowBattery
195
+ this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10
196
+ ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
197
+ : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
198
+ await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`)
199
+ }
200
+
201
+ async openAPIparseStatus(): Promise<void> {
202
+ await this.debugLog('openAPIparseStatus')
203
+ await this.debugLog(`(temperature, humidity) = OpenAPI:(${this.deviceStatus.temperature}, ${this.deviceStatus.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`)
204
+
205
+ // CurrentRelativeHumidity
206
+ if (!(this.device as meterConfig).hide_humidity && this.HumiditySensor?.Service) {
207
+ this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity
208
+ await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`)
209
+ }
210
+
211
+ // CurrentTemperature
212
+ if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
213
+ this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature
214
+ await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`)
215
+ }
216
+
217
+ // BatteryLevel
218
+ this.Battery.BatteryLevel = this.deviceStatus.battery
219
+ await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`)
220
+
221
+ // StatusLowBattery
222
+ this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10
223
+ ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
224
+ : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
225
+ await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`)
226
+
227
+ // FirmwareVersion
228
+ if (this.deviceStatus.version) {
229
+ const version = this.deviceStatus.version.toString()
230
+ this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`)
231
+ const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'
232
+ this.accessory
233
+ .getService(this.hap.Service.AccessoryInformation)!
234
+ .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion)
235
+ .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion)
236
+ .getCharacteristic(this.hap.Characteristic.FirmwareRevision)
237
+ .updateValue(deviceVersion)
238
+ this.accessory.context.version = deviceVersion
239
+ this.debugSuccessLog(`v: ${this.accessory.context.version}`)
240
+ }
241
+ }
242
+
243
+ async parseStatusWebhook(): Promise<void> {
244
+ await this.debugLog('parseStatusWebhook')
245
+ await this.debugLog(`(scale, temperature, humidity) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, (this.device as meterConfig).convertUnitTo)}, ${this.webhookContext.humidity}) current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`)
246
+ // Check if the scale is not CELSIUS
247
+ if (this.webhookContext.scale !== 'CELSIUS' && (this.device as meterConfig).convertUnitTo === undefined) {
248
+ await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings, if displaying incorrectly in HomeKit.`)
249
+ }
250
+
251
+ // CurrentRelativeHumidity
252
+ if (!(this.device as meterConfig).hide_humidity && this.HumiditySensor?.Service) {
253
+ this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity
254
+ await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`)
255
+ }
256
+
257
+ // CurrentTemperature
258
+ if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
259
+ this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, (this.device as meterConfig).convertUnitTo)
260
+ await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`)
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Asks the SwitchBot API for the latest device information
266
+ */
267
+ async refreshStatus(): Promise<void> {
268
+ if (!this.device.enableCloudService && this.OpenAPI) {
269
+ await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`)
270
+ } else if (this.BLE) {
271
+ await this.BLERefreshStatus()
272
+ } else if (this.OpenAPI && this.platform.config.credentials?.token) {
273
+ await this.openAPIRefreshStatus()
274
+ } else {
275
+ await this.offlineOff()
276
+ await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`)
277
+ }
278
+ }
279
+
280
+ async BLERefreshStatus(): Promise<void> {
281
+ await this.debugLog('BLERefreshStatus')
282
+ const switchBotBLE = await this.switchbotBLE()
283
+ if (switchBotBLE === undefined) {
284
+ await this.BLERefreshConnection(switchBotBLE)
285
+ } else {
286
+ // Start to monitor advertisement packets
287
+ (async () => {
288
+ // Start to monitor advertisement packets
289
+ const serviceData = await this.monitorAdvertisementPackets(switchBotBLE) as meterProServiceData
290
+ // Update HomeKit
291
+ if (serviceData.model === SwitchBotBLEModel.MeterPro && serviceData.modelName === SwitchBotBLEModelName.MeterPro) {
292
+ this.serviceData = serviceData
293
+ await this.BLEparseStatus()
294
+ await this.updateHomeKitCharacteristics()
295
+ } else {
296
+ await this.errorLog(`failed to get serviceData, serviceData: ${JSON.stringify(serviceData)}`)
297
+ await this.BLERefreshConnection(switchBotBLE)
298
+ }
299
+ })()
300
+ }
301
+ }
302
+
303
+ async registerPlatformBLE(): Promise<void> {
304
+ await this.debugLog('registerPlatformBLE')
305
+ if (this.config.options?.BLE) {
306
+ await this.debugLog('is listening to Platform BLE.')
307
+ try {
308
+ const formattedDeviceId = formatDeviceIdAsMac(this.device.deviceId)
309
+ this.device.bleMac = formattedDeviceId
310
+ await this.debugLog(`bleMac: ${this.device.bleMac}`)
311
+ this.platform.bleEventHandler[this.device.bleMac] = async (context: meterProServiceData) => {
312
+ try {
313
+ await this.debugLog(`received BLE: ${JSON.stringify(context)}`)
314
+ this.serviceData = context
315
+ await this.BLEparseStatus()
316
+ await this.updateHomeKitCharacteristics()
317
+ } catch (e: any) {
318
+ await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e.message ?? e}`)
319
+ }
320
+ }
321
+ } catch (error) {
322
+ await this.errorLog(`failed to format device ID as MAC, Error: ${error}`)
323
+ }
324
+ } else {
325
+ await this.debugLog('is not listening to Platform BLE')
326
+ }
327
+ }
328
+
329
+ async openAPIRefreshStatus(): Promise<void> {
330
+ await this.debugLog('openAPIRefreshStatus')
331
+ try {
332
+ const { body } = await this.deviceRefreshStatus()
333
+ const deviceStatus: any = await body
334
+ await this.debugLog(`statusCode: ${deviceStatus.statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`)
335
+ if (await this.successfulStatusCodes(deviceStatus)) {
336
+ await this.debugSuccessLog(`statusCode: ${deviceStatus.statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`)
337
+ this.deviceStatus = deviceStatus.body
338
+ await this.openAPIparseStatus()
339
+ await this.updateHomeKitCharacteristics()
340
+ } else {
341
+ await this.debugWarnLog(`statusCode: ${deviceStatus.statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`)
342
+ await this.debugWarnLog(deviceStatus)
343
+ }
344
+ } catch (e: any) {
345
+ await this.apiError(e)
346
+ await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`)
347
+ }
348
+ }
349
+
350
+ async registerWebhook() {
351
+ if (this.device.webhook) {
352
+ await this.debugLog('is listening webhook.')
353
+ this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterProWebhookContext) => {
354
+ try {
355
+ await this.debugLog(`received Webhook: ${JSON.stringify(context)}`)
356
+ this.webhookContext = context
357
+ await this.parseStatusWebhook()
358
+ await this.updateHomeKitCharacteristics()
359
+ } catch (e: any) {
360
+ await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e.message ?? e}`)
361
+ }
362
+ }
363
+ } else {
364
+ await this.debugLog('is not listening webhook.')
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Updates the status for each of the HomeKit Characteristics
370
+ */
371
+ async updateHomeKitCharacteristics(): Promise<void> {
372
+ // CurrentRelativeHumidity
373
+ if (!(this.device as meterConfig).hide_humidity && this.HumiditySensor?.Service) {
374
+ await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity')
375
+ }
376
+ // CurrentTemperature
377
+ if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
378
+ await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature')
379
+ }
380
+ // BatteryLevel
381
+ await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel')
382
+ // StatusLowBattery
383
+ await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery')
384
+ }
385
+
386
+ async BLERefreshConnection(switchbot: any): Promise<void> {
387
+ await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`)
388
+ if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') {
389
+ await this.warnLog('Using OpenAPI Connection to Refresh Status')
390
+ await this.openAPIRefreshStatus()
391
+ }
392
+ }
393
+
394
+ async offlineOff(): Promise<void> {
395
+ if (this.device.offline) {
396
+ if (!(this.device as meterConfig).hide_humidity && this.HumiditySensor?.Service) {
397
+ this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, 50)
398
+ }
399
+ if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
400
+ this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, 30)
401
+ }
402
+ this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, 100)
403
+ this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL)
404
+ }
405
+ }
406
+
407
+ async apiError(e: any): Promise<void> {
408
+ if (!(this.device as meterConfig).hide_humidity && this.HumiditySensor?.Service) {
409
+ this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e)
410
+ }
411
+ if (!(this.device as meterConfig).hide_temperature && this.TemperatureSensor?.Service) {
412
+ this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e)
413
+ }
414
+ this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e)
415
+ this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e)
416
+ }
417
+ }
package/src/platform.ts CHANGED
@@ -39,6 +39,7 @@ import { StripLight } from './device/lightstrip.js'
39
39
  import { Lock } from './device/lock.js'
40
40
  import { Meter } from './device/meter.js'
41
41
  import { MeterPlus } from './device/meterplus.js'
42
+ import { MeterPro } from './device/meterpro.js'
42
43
  import { Motion } from './device/motion.js'
43
44
  import { Plug } from './device/plug.js'
44
45
  import { RobotVacuumCleaner } from './device/robotvacuumcleaner.js'
@@ -630,6 +631,7 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
630
631
  'Meter': this.createMeter.bind(this),
631
632
  'MeterPlus': this.createMeterPlus.bind(this),
632
633
  'Meter Plus (JP)': this.createMeterPlus.bind(this),
634
+ 'Meter Pro': this.createMeterPro.bind(this),
633
635
  'WoIOSensor': this.createIOSensor.bind(this),
634
636
  'Water Detector': this.createWaterDetector.bind(this),
635
637
  'Motion Sensor': this.createMotion.bind(this),
@@ -960,6 +962,69 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin {
960
962
  }
961
963
  }
962
964
 
965
+ private async createMeterPro(device: device & devicesConfig) {
966
+ const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`)
967
+
968
+ // see if an accessory with the same uuid has already been registered and restored from
969
+ // the cached devices we stored in the `configureAccessory` method above
970
+ const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid)
971
+
972
+ if (existingAccessory) {
973
+ // the accessory already exists
974
+ if (await this.registerDevice(device)) {
975
+ // console.log("existingAccessory", existingAccessory);
976
+ // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
977
+ existingAccessory.context.device = device
978
+ existingAccessory.context.model = SwitchBotModel.MeterPro ?? SwitchBotModel.MeterProCO2
979
+ existingAccessory.context.deviceId = device.deviceId
980
+ existingAccessory.context.deviceType = device.deviceType
981
+ existingAccessory.displayName = device.configDeviceName
982
+ ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
983
+ : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
984
+ existingAccessory.context.connectionType = await this.connectionType(device)
985
+ existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'
986
+ this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`)
987
+ this.api.updatePlatformAccessories([existingAccessory])
988
+ // create the accessory handler for the restored accessory
989
+ // this is imported from `platformAccessory.ts`
990
+ new MeterPro(this, existingAccessory, device)
991
+ await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`)
992
+ } else {
993
+ this.unregisterPlatformAccessories(existingAccessory)
994
+ }
995
+ } else if (await this.registerDevice(device)) {
996
+ // create a new accessory
997
+ const accessory = new this.api.platformAccessory(device.configDeviceName
998
+ ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
999
+ : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid)
1000
+
1001
+ // store a copy of the device object in the `accessory.context`
1002
+ // the `context` property can be used to store any data about the accessory you may need
1003
+ accessory.context.device = device
1004
+ accessory.context.model = SwitchBotModel.MeterPro ?? SwitchBotModel.MeterProCO2
1005
+ accessory.context.deviceId = device.deviceId
1006
+ accessory.context.deviceType = device.deviceType
1007
+ accessory.displayName = device.configDeviceName
1008
+ ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName)
1009
+ : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName)
1010
+ accessory.context.connectionType = await this.connectionType(device)
1011
+ accessory.context.connectionType = await this.connectionType(device)
1012
+ accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'
1013
+ const newOrExternal = !device.external ? 'Adding new' : 'Loading external'
1014
+ await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`)
1015
+ // create the accessory handler for the newly create accessory
1016
+ // this is imported from `platformAccessory.ts`
1017
+ new MeterPro(this, accessory, device)
1018
+ await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`)
1019
+
1020
+ // publish device externally or link the accessory to your platform
1021
+ this.externalOrPlatform(device, accessory)
1022
+ this.accessories.push(accessory)
1023
+ } else {
1024
+ await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`)
1025
+ }
1026
+ }
1027
+
963
1028
  private async createHub2(device: device & devicesConfig) {
964
1029
  const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`)
965
1030
 
@@ -1,81 +0,0 @@
1
- #!/bin/env node
2
-
3
- /**
4
- * This scripts queries the npm registry to pull out the latest version for a given tag.
5
- */
6
-
7
- const fs = require("fs");
8
- const semver = require("semver");
9
- const child_process = require("child_process");
10
- const assert = require("assert");
11
-
12
- const BRANCH_VERSION_PATTERN = /^([A-Za-z]*)-(\d+.\d+.\d+)$/
13
-
14
- // Load the contents of the package.json file
15
- const packageJSON = JSON.parse(fs.readFileSync("package.json", "utf8"));
16
-
17
- let refArgument = process.argv[2];
18
- let tagArgument = process.argv[3] || "latest";
19
-
20
- if (refArgument == null) {
21
- console.error("ref argument is missing");
22
- console.error("Usage: npm-version-script.cjs <ref> [tag]");
23
- process.exit(1);
24
- }
25
-
26
- /**
27
- * Queries the NPM registry for the latest version for the provided tag.
28
- * @param tag The tag to query for.
29
- * @returns {string} Returns the version.
30
- */
31
- function getTagVersionFromNpm(tag) {
32
- try {
33
- return child_process.execSync(`npm info ${packageJSON.name} version --tag="${tag}"`).toString("utf8").trim();
34
- } catch (e) {
35
- throw e;
36
- }
37
- }
38
-
39
- function desiredTargetVersion(ref) {
40
- // ref is a GitHub action ref string
41
- if (ref.startsWith("refs/pull/")) {
42
- throw Error("The version script was executed inside a PR!");
43
- }
44
-
45
- assert(ref.startsWith("refs/heads/"))
46
- let branchName = ref.slice("refs/heads/".length);
47
-
48
- let results = branchName.match(BRANCH_VERSION_PATTERN);
49
- if (results != null) {
50
- if (results[1] !== tagArgument) {
51
- console.warn(`The base branch name (${results[1]}) differs from the tag name ${tagArgument}`);
52
- }
53
-
54
- return results[2];
55
- }
56
-
57
- // legacy mode were we use the `betaVersion` property in the package.json
58
- if (branchName === "beta" && packageJSON.betaVersion) {
59
- return packageJSON.betaVersion
60
- }
61
-
62
- throw new Error("Malformed branch name for ref: " + ref + ". Can't derive the base version. Use a branch name like: beta-x.x.x!");
63
- }
64
-
65
- // derive the base version from the branch ref
66
- const baseVersion = desiredTargetVersion(refArgument);
67
-
68
- // query the npm registry for the latest version of the provided tag name
69
- const latestReleasedVersion = getTagVersionFromNpm(tagArgument); // e.g. 0.7.0-beta.12
70
- const latestReleaseBase = semver.inc(latestReleasedVersion, "patch"); // will produce 0.7.0 (removing the preid, needed for the equality check below)
71
-
72
- let publishTag;
73
- if (semver.eq(baseVersion, latestReleaseBase)) { // check if we are releasing another version for the latest beta
74
- publishTag = latestReleasedVersion; // set the current latest beta to be incremented
75
- } else {
76
- publishTag = baseVersion; // start of with a new beta version
77
- }
78
-
79
- // save the package.json
80
- packageJSON.version = publishTag;
81
- fs.writeFileSync("package.json", JSON.stringify(packageJSON, null, 2));