@switchbot/homebridge-switchbot 4.0.1-beta.4 → 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/CHANGELOG.md +9 -0
- package/dist/device/device.d.ts +1 -1
- package/dist/device/meterpro.d.ts +42 -0
- package/dist/device/meterpro.d.ts.map +1 -0
- package/dist/device/meterpro.js +369 -0
- package/dist/device/meterpro.js.map +1 -0
- package/dist/device/motion.d.ts.map +1 -1
- package/dist/device/motion.js +1 -3
- package/dist/device/motion.js.map +1 -1
- package/dist/platform.d.ts +1 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +63 -0
- package/dist/platform.js.map +1 -1
- package/package.json +4 -4
- package/src/device/meterpro.ts +417 -0
- package/src/device/motion.ts +1 -3
- package/src/platform.ts +65 -0
- package/.github/npm-version-script.cjs +0 -81
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.
|
|
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.
|
|
84
|
+
"node-switchbot": "^3.2.1",
|
|
85
85
|
"rxjs": "^7.8.1"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
@@ -90,14 +90,14 @@
|
|
|
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.
|
|
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",
|
|
97
97
|
"eslint": "^9.13.0",
|
|
98
98
|
"eslint-plugin-format": "^0.1.2",
|
|
99
99
|
"homebridge": "^1.8.4",
|
|
100
|
-
"homebridge-config-ui-x": "4.
|
|
100
|
+
"homebridge-config-ui-x": "4.62.0",
|
|
101
101
|
"nodemon": "^3.1.7",
|
|
102
102
|
"npm-check-updates": "^17.1.4",
|
|
103
103
|
"shx": "^0.3.4",
|
|
@@ -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/device/motion.ts
CHANGED
|
@@ -120,9 +120,7 @@ export class Motion extends deviceBase {
|
|
|
120
120
|
accessory.context.LightSensor = this.LightSensor as object
|
|
121
121
|
|
|
122
122
|
// Initialize LightSensor Characteristics
|
|
123
|
-
this.LightSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true)
|
|
124
|
-
return this.LightSensor!.CurrentAmbientLightLevel
|
|
125
|
-
})
|
|
123
|
+
this.LightSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true)
|
|
126
124
|
};
|
|
127
125
|
|
|
128
126
|
// Retrieve initial values and updateHomekit
|
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));
|