bt-sensors-plugin-sk 1.1.1 → 1.2.0-beta.0.0.10
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/BTSensor.js +304 -108
- package/Queue.js +48 -0
- package/README.md +36 -22
- package/classLoader.js +38 -0
- package/definitions.json +941 -0
- package/index.js +425 -375
- package/package.json +52 -6
- package/plugin_defaults.json +146 -0
- package/public/893.js +1 -1
- package/public/main.js +1 -100
- package/public/remoteEntry.js +1 -129
- package/sensor_classes/ATC.js +34 -22
- package/sensor_classes/Aranet/AranetSensor.js +4 -0
- package/sensor_classes/Aranet2.js +16 -15
- package/sensor_classes/Aranet4.js +23 -17
- package/sensor_classes/BlackListedDevice.js +4 -0
- package/sensor_classes/DEVELOPMENT.md +1 -6
- package/sensor_classes/GoveeH50xx.js +12 -12
- package/sensor_classes/GoveeH510x.js +7 -8
- package/sensor_classes/IBeacon.js +6 -10
- package/sensor_classes/Inkbird.js +8 -6
- package/sensor_classes/JBDBMS.js +33 -25
- package/sensor_classes/KilovaultHLXPlus.js +33 -16
- package/sensor_classes/LancolVoltageMeter.js +4 -3
- package/sensor_classes/MopekaTankSensor.js +31 -15
- package/sensor_classes/Renogy/RenogySensor.js +15 -6
- package/sensor_classes/RenogyBattery.js +15 -13
- package/sensor_classes/RenogyRoverClient.js +2 -3
- package/sensor_classes/RuuviTag.js +36 -27
- package/sensor_classes/ShellySBHT003C.js +19 -22
- package/sensor_classes/SwitchBotMeterPlus.js +10 -12
- package/sensor_classes/SwitchBotTH.js +13 -16
- package/sensor_classes/UNKNOWN.js +8 -4
- package/sensor_classes/UltrasonicWindMeter.js +13 -5
- package/sensor_classes/Victron/VictronSensor.js +6 -2
- package/sensor_classes/VictronACCharger.js +19 -8
- package/sensor_classes/VictronBatteryMonitor.js +49 -37
- package/sensor_classes/VictronDCDCConverter.js +14 -5
- package/sensor_classes/VictronDCEnergyMeter.js +27 -15
- package/sensor_classes/VictronGXDevice.js +2 -5
- package/sensor_classes/VictronInverter.js +19 -15
- package/sensor_classes/VictronInverterRS.js +19 -8
- package/sensor_classes/VictronLynxSmartBMS.js +15 -13
- package/sensor_classes/VictronOrionXS.js +16 -3
- package/sensor_classes/VictronSmartBatteryProtect.js +5 -6
- package/sensor_classes/VictronSmartLithium.js +18 -18
- package/sensor_classes/VictronSolarCharger.js +31 -18
- package/sensor_classes/VictronVEBus.js +2 -5
- package/sensor_classes/XiaomiMiBeacon.js +31 -21
- package/spec/electrical.json +688 -0
- package/spec/environment.json +401 -0
- package/spec/sensors.json +39 -0
- package/spec/tanks.json +115 -0
- package/src/components/PluginConfigurationPanel.js +393 -0
- package/src/index.js +0 -0
- package/webpack.config.js +71 -0
package/index.js
CHANGED
|
@@ -1,41 +1,57 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const util = require('util')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const semver = require('semver')
|
|
5
1
|
const packageInfo = require("./package.json")
|
|
6
2
|
|
|
7
3
|
const {createBluetooth} = require('node-ble')
|
|
8
|
-
const {
|
|
4
|
+
const {Variant} = require('dbus-next')
|
|
9
5
|
const {bluetooth, destroy} = createBluetooth()
|
|
10
6
|
|
|
11
7
|
const BTSensor = require('./BTSensor.js')
|
|
12
8
|
const BLACKLISTED = require('./sensor_classes/BlackListedDevice.js')
|
|
9
|
+
const { createChannel, createSession } = require("better-sse");
|
|
10
|
+
const { clearTimeout } = require('timers')
|
|
11
|
+
const loadClassMap = require('./classLoader.js')
|
|
13
12
|
|
|
14
13
|
class MissingSensor {
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
constructor(config){
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
17
|
+
this.config=config
|
|
18
|
+
this.addPath=BTSensor.prototype.addPath.bind(this)
|
|
19
|
+
this.addParameter=BTSensor.prototype.addParameter.bind(this)
|
|
20
|
+
this.addDefaultPath=BTSensor.prototype.addDefaultPath.bind(this)
|
|
21
|
+
this.addDefaultParam=BTSensor.prototype.addDefaultParam.bind(this)
|
|
22
|
+
this.getPath=BTSensor.prototype.getPath.bind(this)
|
|
22
23
|
|
|
23
|
-
this.
|
|
24
|
+
this.getJSONSchema = BTSensor.prototype.getJSONSchema.bind(this)
|
|
25
|
+
this.initSchema = BTSensor.prototype.initSchema.bind(this)
|
|
26
|
+
|
|
27
|
+
this.initSchema()
|
|
24
28
|
var keys = Object.keys(config?.paths??{})
|
|
25
|
-
|
|
29
|
+
|
|
26
30
|
keys.forEach((key)=>{
|
|
27
|
-
this.
|
|
31
|
+
this.addPath(key,
|
|
32
|
+
{ type: config.paths[key]?.type??'string',
|
|
33
|
+
title: config.paths[key].title} )
|
|
28
34
|
} )
|
|
29
35
|
keys = Object.keys(config?.params??{})
|
|
30
36
|
keys.forEach((key)=>{
|
|
31
|
-
this.
|
|
37
|
+
this.addParameter(key,
|
|
38
|
+
{ type: config.params[key]?.type??'string',
|
|
39
|
+
title: config.params[key].title
|
|
40
|
+
})
|
|
32
41
|
this[key]=config.params[key]
|
|
33
42
|
})
|
|
34
43
|
this.mac_address = config.mac_address
|
|
35
44
|
|
|
36
45
|
}
|
|
37
46
|
hasGATT(){
|
|
38
|
-
return
|
|
47
|
+
return this.config.gattParams
|
|
48
|
+
}
|
|
49
|
+
initGATTConnection(){
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getGATTDescription(){
|
|
54
|
+
return ""
|
|
39
55
|
}
|
|
40
56
|
getMetadata(){
|
|
41
57
|
return this.metadata
|
|
@@ -47,409 +63,404 @@ class MissingSensor {
|
|
|
47
63
|
return ""
|
|
48
64
|
}
|
|
49
65
|
getName(){
|
|
50
|
-
return this?.name??"Unknown device"
|
|
66
|
+
return `${this?.name??"Unknown device"} (OUT OF RANGE)`
|
|
51
67
|
}
|
|
52
68
|
getDisplayName(){
|
|
53
|
-
return `
|
|
69
|
+
return `(${this.getName()} ${this.getMacAddress()})`
|
|
70
|
+
}
|
|
71
|
+
getRSSI(){
|
|
72
|
+
return NaN
|
|
54
73
|
}
|
|
55
74
|
stopListening(){}
|
|
56
75
|
listen(){}
|
|
76
|
+
isActive(){
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
elapsedTimeSinceLastContact(){
|
|
80
|
+
return NaN
|
|
81
|
+
}
|
|
82
|
+
getSignalStrength(){
|
|
83
|
+
return NaN
|
|
84
|
+
}
|
|
85
|
+
|
|
57
86
|
}
|
|
58
87
|
module.exports = function (app) {
|
|
59
|
-
var adapterID = 'hci0'
|
|
60
|
-
|
|
61
|
-
|
|
62
88
|
var deviceConfigs
|
|
63
89
|
var starts=0
|
|
64
|
-
var classMap
|
|
65
|
-
|
|
66
|
-
var utilities_sk
|
|
67
90
|
|
|
68
91
|
var plugin = {};
|
|
69
92
|
plugin.id = 'bt-sensors-plugin-sk';
|
|
70
93
|
plugin.name = 'BT Sensors plugin';
|
|
71
94
|
plugin.description = 'Plugin to communicate with and update paths to BLE Sensors in Signalk';
|
|
72
|
-
|
|
73
|
-
|
|
74
95
|
|
|
75
|
-
//Try and load utilities-sk NOTE: should be installed from App Store--
|
|
76
|
-
//But there's a fail safe because I'm a reasonable man.
|
|
77
|
-
|
|
78
|
-
utilities_sk = {
|
|
79
|
-
loadClasses: function(dir, ext='.js')
|
|
80
|
-
{
|
|
81
|
-
const classMap = new Map()
|
|
82
|
-
const classFiles = fs.readdirSync(dir)
|
|
83
|
-
.filter(file => file.endsWith(ext));
|
|
84
|
-
|
|
85
|
-
classFiles.forEach(file => {
|
|
86
|
-
const filePath = path.join(dir, file);
|
|
87
|
-
const cls = require(filePath);
|
|
88
|
-
classMap.set(cls.name, cls);
|
|
89
|
-
})
|
|
90
|
-
return classMap
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function sleep(x) {
|
|
95
|
-
return new Promise((resolve) => {
|
|
96
|
-
setTimeout(() => {
|
|
97
|
-
resolve(x);
|
|
98
|
-
}, x);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
async function instantiateSensor(device,config){
|
|
102
|
-
try{
|
|
103
|
-
for (var [clsName, cls] of classMap) {
|
|
104
|
-
if (clsName.startsWith("_")) continue
|
|
105
|
-
const c = await cls.identify(device)
|
|
106
|
-
if (c) {
|
|
107
|
-
c.debug=app.debug
|
|
108
|
-
const sensor = new c(device,config?.params, config?.gattParams)
|
|
109
|
-
sensor.debug=app.debug
|
|
110
|
-
await sensor.init()
|
|
111
|
-
app.debug(`instantiated ${await BTSensor.getDeviceProp(device,"Address")}`)
|
|
112
|
-
|
|
113
|
-
return sensor
|
|
114
|
-
}
|
|
115
|
-
}} catch(error){
|
|
116
|
-
const msg = `Unable to instantiate ${await BTSensor.getDeviceProp(device,"Address")}: ${error.message} `
|
|
117
|
-
app.debug(msg)
|
|
118
|
-
app.debug(error)
|
|
119
|
-
app.setPluginError(msg)
|
|
120
|
-
}
|
|
121
|
-
//if we're here ain't got no class for the device
|
|
122
|
-
var sensor
|
|
123
|
-
if (config.params?.sensorClass){
|
|
124
|
-
const c = classMap.get(config.params.sensorClass)
|
|
125
|
-
c.debug=app.debug
|
|
126
|
-
sensor = new c(device,config?.params, config?.gattParams)
|
|
127
|
-
sensor.debug=app.debug
|
|
128
|
-
await sensor.init()
|
|
129
|
-
} else{
|
|
130
|
-
sensor = new (classMap.get('UNKNOWN'))(device)
|
|
131
|
-
await sensor.init()
|
|
132
|
-
}
|
|
133
|
-
return sensor
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function createPaths(config){
|
|
137
|
-
config.sensor.getMetadata().forEach((metadatum, tag)=>{
|
|
138
|
-
if ((!(metadatum?.isParam)??false)){ //param metadata is passed to the sensor at
|
|
139
|
-
//create time through the constructor, and isn't a
|
|
140
|
-
//a value you want to see in a path
|
|
141
|
-
|
|
142
|
-
const path = config.paths[tag]
|
|
143
|
-
if (!(path===undefined))
|
|
144
|
-
app.handleMessage(plugin.id,
|
|
145
|
-
{
|
|
146
|
-
updates:
|
|
147
|
-
[{ meta: [{path: path, value: { units: metadatum?.unit }}]}]
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function initPaths(deviceConfig){
|
|
154
|
-
deviceConfig.sensor.getMetadata().forEach((metadatum, tag)=>{
|
|
155
|
-
const path = deviceConfig.paths[tag];
|
|
156
|
-
if (!(path === undefined))
|
|
157
|
-
deviceConfig.sensor.on(tag, (val)=>{
|
|
158
|
-
if (metadatum.notify){
|
|
159
|
-
app.notify( tag, val, plugin.id )
|
|
160
|
-
} else {
|
|
161
|
-
updatePath(path,val)
|
|
162
|
-
}
|
|
163
|
-
})
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
function updatePath(path, val){
|
|
167
|
-
app.handleMessage(plugin.id, {updates: [ { values: [ {path: path, value: val }] } ] })
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function loadClassMap() {
|
|
171
|
-
const _classMap = utilities_sk.loadClasses(path.join(__dirname, 'sensor_classes'))
|
|
172
|
-
classMap = new Map([..._classMap].filter(([k, v]) => !k.startsWith("_") ))
|
|
173
|
-
const libPath = app.config.appPath +(
|
|
174
|
-
semver.gt(app.config.version,"2.13.5")?"dist":"lib"
|
|
175
|
-
)
|
|
176
|
-
//+ app.config.version
|
|
177
|
-
import(libPath+"/modules.js").then( (modulesjs)=>{
|
|
178
|
-
const { default:defaultExport} = modulesjs
|
|
179
|
-
const modules = defaultExport.modulesWithKeyword(app.config, "signalk-bt-sensor-class")
|
|
180
|
-
modules.forEach((module)=>{
|
|
181
|
-
module.metadata.classFiles.forEach((classFile)=>{
|
|
182
|
-
const cls = require(module.location+module.module+"/"+classFile);
|
|
183
|
-
classMap.set(cls.name, cls);
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
classMap.get('UNKNOWN').classMap=new Map([...classMap].sort().filter(([k, v]) => !v.isSystem )) // share the classMap with Unknown for configuration purposes
|
|
187
|
-
})
|
|
188
|
-
}
|
|
189
|
-
|
|
190
96
|
app.debug(`Loading plugin ${packageInfo.version}`)
|
|
191
97
|
|
|
192
98
|
plugin.schema = {
|
|
193
99
|
type: "object",
|
|
194
|
-
description: "NOTE: \n 1) Plugin must be enabled to configure your sensors. \n"+
|
|
195
|
-
"2) You will have to wait until the scanner has found your device before seeing your device's config fields and saving the configuration. \n"+
|
|
196
|
-
"3) To refresh the list of available devices and their configurations, just open and close the config screen by clicking on the arrow symbol in the config's top bar. \n"+
|
|
197
|
-
"4) If you submit and get errors it may be because the configured devices have not yet all been discovered.",
|
|
198
100
|
required:["adapter","discoveryTimeout", "discoveryInterval"],
|
|
199
101
|
properties: {
|
|
200
102
|
adapter: {title: "Bluetooth adapter",
|
|
201
103
|
type: "string", default: "hci0"},
|
|
202
104
|
transport: {title: "Transport ",
|
|
203
|
-
|
|
204
|
-
|
|
105
|
+
type: "string", enum: ["auto","le","bredr"], default: "le", enumNames:["Auto", "LE-Bluetooth Low Energy", "BR/EDR Bluetooth basic rate/enhanced data rate"]},
|
|
205
106
|
discoveryTimeout: {title: "Default device discovery timeout (in seconds)",
|
|
206
107
|
type: "integer", default: 30,
|
|
207
108
|
minimum: 10,
|
|
208
109
|
maximum: 3600
|
|
209
110
|
},
|
|
210
|
-
|
|
211
111
|
discoveryInterval: {title: "Scan for new devices interval (in seconds-- 0 for no new device scanning)",
|
|
212
112
|
type: "integer",
|
|
213
113
|
default: 10,
|
|
214
|
-
minimum: 0
|
|
215
|
-
multipleOf: 10
|
|
114
|
+
minimum: 0
|
|
216
115
|
},
|
|
217
|
-
peripherals:
|
|
218
|
-
{ type: "array", title: "Sensors", items:{
|
|
219
|
-
title: "", type: "object",
|
|
220
|
-
required:["mac_address", "discoveryTimeout"],
|
|
221
|
-
properties:{
|
|
222
|
-
active: {title: "Active", type: "boolean", default: true },
|
|
223
|
-
mac_address: {title: "Bluetooth Sensor", type: "string" },
|
|
224
|
-
discoveryTimeout: {title: "Device discovery timeout (in seconds)",
|
|
225
|
-
type: "integer", default:30,
|
|
226
|
-
minimum: 10,
|
|
227
|
-
maximum: 600
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
116
|
}
|
|
234
117
|
}
|
|
235
|
-
const UI_SCHEMA =
|
|
236
|
-
{ "peripherals": {
|
|
237
|
-
'ui:disabled': !plugin.started
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
plugin.uiSchema=UI_SCHEMA
|
|
241
|
-
|
|
242
|
-
function updateSensorDisplayName(sensor){
|
|
243
|
-
const mac_address = sensor.getMacAddress()
|
|
244
|
-
const displayName = sensor.getDisplayName()
|
|
245
|
-
|
|
246
|
-
const mac_addresses_list = plugin.schema.properties.peripherals.items.properties.
|
|
247
|
-
mac_address.enum
|
|
248
|
-
const mac_addresses_names = plugin.schema.properties.peripherals.items.properties.
|
|
249
|
-
mac_address.enumNames
|
|
250
|
-
|
|
251
|
-
var index = mac_addresses_list.indexOf(mac_address)
|
|
252
|
-
if (index!=-1)
|
|
253
|
-
mac_addresses_names[index]= displayName
|
|
254
|
-
}
|
|
255
118
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
119
|
+
|
|
120
|
+
plugin.started=false
|
|
121
|
+
|
|
122
|
+
var discoveryIntervalID, progressID, progressTimeoutID, deviceHealthID
|
|
123
|
+
var adapter
|
|
124
|
+
const channel = createChannel()
|
|
125
|
+
const classMap = loadClassMap(app)
|
|
126
|
+
const sensorMap=new Map()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
/* plugin.registerWithRouter = function(router) {
|
|
130
|
+
router.get('/sendPluginState', async (req, res) => {
|
|
261
131
|
|
|
262
|
-
|
|
263
|
-
|
|
132
|
+
res.status(200).json({
|
|
133
|
+
"state":(plugin.started?"started":"stopped")
|
|
134
|
+
})
|
|
135
|
+
});
|
|
136
|
+
router.get("/sse", async (req, res) => {
|
|
137
|
+
const session = await createSession(req, res);
|
|
138
|
+
channel.register(session)
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
}*/
|
|
142
|
+
|
|
143
|
+
plugin.start = async function (options, restartPlugin) {
|
|
144
|
+
plugin.started=true
|
|
145
|
+
var adapterID=options.adapter
|
|
146
|
+
var foundConfiguredDevices=0
|
|
264
147
|
|
|
265
|
-
|
|
266
|
-
mac_addresses_names.splice(i,1)
|
|
267
|
-
oneOf.splice(oneOf.findIndex((p)=>p.properties.mac_address.enum[0]==mac_address),1)
|
|
148
|
+
plugin.registerWithRouter = function(router) {
|
|
268
149
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
150
|
+
router.post('/updateSensorData', async (req, res) => {
|
|
151
|
+
app.debug(req.body)
|
|
152
|
+
const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
|
|
153
|
+
if (i<0){
|
|
154
|
+
if (!options.peripherals){
|
|
155
|
+
if (!options.hasOwnProperty("peripherals"))
|
|
156
|
+
options.peripherals=[]
|
|
157
|
+
|
|
158
|
+
options.peripherals=[]
|
|
159
|
+
}
|
|
160
|
+
options.peripherals.push(req.body)
|
|
161
|
+
} else {
|
|
162
|
+
options.peripherals[i] = req.body
|
|
163
|
+
}
|
|
164
|
+
app.savePluginOptions(
|
|
165
|
+
options, async () => {
|
|
166
|
+
app.debug('Plugin options saved')
|
|
167
|
+
res.status(200).json({message: "Sensor updated"})
|
|
168
|
+
const sensor = sensorMap.get(req.body.mac_address)
|
|
169
|
+
if (sensor) {
|
|
170
|
+
removeSensorFromList(sensor)
|
|
171
|
+
if (sensor.isActive())
|
|
172
|
+
await sensor.stopListening()
|
|
173
|
+
initConfiguredDevice(req.body)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
});
|
|
180
|
+
router.post('/removeSensorData', async (req, res) => {
|
|
181
|
+
app.debug(req.body)
|
|
182
|
+
const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
|
|
183
|
+
if (i>=0){
|
|
184
|
+
deviceConfigs.splice(i,1)
|
|
185
|
+
}
|
|
186
|
+
if (sensorMap.has(req.body.mac_address))
|
|
187
|
+
sensorMap.delete(req.body.mac_address)
|
|
188
|
+
app.savePluginOptions(
|
|
189
|
+
options, () => {
|
|
190
|
+
app.debug('Plugin options saved')
|
|
191
|
+
res.status(200).json({message: "Sensor updated"})
|
|
192
|
+
channel.broadcast({},"resetSensors")
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
router.post('/updateBaseData', async (req, res) => {
|
|
199
|
+
|
|
200
|
+
app.debug(req.body)
|
|
201
|
+
Object.assign(options,req.body)
|
|
202
|
+
app.savePluginOptions(
|
|
203
|
+
options, () => {
|
|
204
|
+
app.debug('Plugin options saved')
|
|
205
|
+
res.status(200).json({message: "Plugin updated"})
|
|
206
|
+
channel.broadcast({},"pluginRestarted")
|
|
207
|
+
restartPlugin(options)
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
});
|
|
273
211
|
|
|
274
|
-
if(plugin.schema.properties.peripherals.items.properties.mac_address.enum==undefined) {
|
|
275
|
-
plugin.schema.properties.peripherals.items.properties.mac_address.enum=[]
|
|
276
|
-
plugin.schema.properties.peripherals.items.properties.mac_address.enumNames=[]
|
|
277
|
-
}
|
|
278
|
-
const mac_addresses_names = plugin.schema.properties.peripherals.items.properties.mac_address.enumNames
|
|
279
|
-
const mac_addresses_list = plugin.schema.properties.peripherals.items.properties.mac_address.enum
|
|
280
|
-
const mac_address = sensor.getMacAddress()
|
|
281
|
-
const displayName = sensor.getDisplayName()
|
|
282
|
-
|
|
283
|
-
var index = mac_addresses_list.indexOf(mac_address)
|
|
284
|
-
if (index==-1)
|
|
285
|
-
index = mac_addresses_list.push(mac_address)-1
|
|
286
|
-
mac_addresses_names[index]= displayName
|
|
287
|
-
var oneOf = {properties:{mac_address:{enum:[mac_address]}}}
|
|
288
212
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
213
|
+
router.get('/getBaseData', (req, res) => {
|
|
214
|
+
|
|
215
|
+
res.status(200).json(
|
|
216
|
+
{
|
|
217
|
+
schema: plugin.schema,
|
|
218
|
+
data: {
|
|
219
|
+
adapter: options.adapter,
|
|
220
|
+
transport: options.transport,
|
|
221
|
+
discoveryTimeout: options.discoveryTimeout,
|
|
222
|
+
discoveryInterval: options.discoveryInterval
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
})
|
|
227
|
+
router.get('/getSensors', (req, res) => {
|
|
228
|
+
app.debug("Sending sensors")
|
|
229
|
+
const t = sensorsToJSON()
|
|
230
|
+
res.status(200).json(t)
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
router.get('/getProgress', (req, res) => {
|
|
234
|
+
app.debug("Sending progress")
|
|
235
|
+
const json = {"progress":foundConfiguredDevices/deviceConfigs.length, "maxTimeout": 1,
|
|
236
|
+
"deviceCount":foundConfiguredDevices,
|
|
237
|
+
"totalDevices": deviceConfigs.length}
|
|
238
|
+
res.status(200).json(json)
|
|
239
|
+
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
router.get('/getPluginState', async (req, res) => {
|
|
243
|
+
|
|
244
|
+
res.status(200).json({
|
|
245
|
+
"state":(plugin.started?"started":"stopped")
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
router.get("/sse", async (req, res) => {
|
|
249
|
+
const session = await createSession(req, res);
|
|
250
|
+
channel.register(session)
|
|
251
|
+
});
|
|
252
|
+
|
|
298
253
|
|
|
299
|
-
|
|
254
|
+
};
|
|
300
255
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
sensor.getGATTParamMetadata().forEach((metadatum,tag)=>{
|
|
308
|
-
oneOf.properties.gattParams.properties[tag]=metadatum.asJSONSchema()
|
|
309
|
-
})
|
|
256
|
+
function sensorsToJSON(){
|
|
257
|
+
return Array.from(
|
|
258
|
+
Array.from(sensorMap.values()).filter((s)=>!(s instanceof BLACKLISTED) ).map(
|
|
259
|
+
sensorToJSON
|
|
260
|
+
))
|
|
310
261
|
}
|
|
311
262
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
263
|
+
function getSensorInfo(sensor){
|
|
264
|
+
|
|
265
|
+
const etslc = sensor.elapsedTimeSinceLastContact()
|
|
266
|
+
return { mac: sensor.getMacAddress(),
|
|
267
|
+
name: sensor.getName(),
|
|
268
|
+
RSSI: sensor.getRSSI(),
|
|
269
|
+
signalStrength: sensor.getSignalStrength(),
|
|
270
|
+
lastContactDelta: etslc
|
|
271
|
+
}
|
|
317
272
|
}
|
|
318
|
-
sensor.getPathMetadata().forEach((metadatum,tag)=>{
|
|
319
|
-
oneOf.properties.paths.properties[tag]=metadatum.asJSONSchema()
|
|
320
|
-
})
|
|
321
|
-
plugin.schema.properties.peripherals.items.dependencies.mac_address.oneOf.push(oneOf)
|
|
322
|
-
//plugin.schema.properties.peripherals.items.title=sensor.getName()
|
|
323
273
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
274
|
+
function sensorToJSON(sensor){
|
|
275
|
+
const config = getDeviceConfig(sensor.getMacAddress())
|
|
276
|
+
return {
|
|
277
|
+
info: getSensorInfo(sensor),
|
|
278
|
+
schema: sensor.getJSONSchema(),
|
|
279
|
+
config: config?config:{}
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function startScanner(transport) {
|
|
332
284
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
285
|
+
app.debug("Starting scan...");
|
|
286
|
+
//Use adapter.helper directly to get around Adapter::startDiscovery()
|
|
287
|
+
//filter options which can cause issues with Device::Connect()
|
|
288
|
+
//turning off Discovery
|
|
289
|
+
//try {await adapter.startDiscovery()}
|
|
290
|
+
try{
|
|
291
|
+
if (transport) {
|
|
292
|
+
app.debug(`Setting Bluetooth transport option to ${transport}`)
|
|
293
|
+
await adapter.helper.callMethod('SetDiscoveryFilter', {
|
|
294
|
+
Transport: new Variant('s', transport)
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
await adapter.helper.callMethod('StartDiscovery')
|
|
298
|
+
}
|
|
299
|
+
catch (error){
|
|
300
|
+
app.debug(error)
|
|
348
301
|
}
|
|
349
|
-
|
|
350
|
-
.catch((e)=>{
|
|
351
|
-
if (s)
|
|
352
|
-
s.stopListening()
|
|
353
|
-
|
|
354
|
-
app.debug(`Unable to communicate with device ${deviceNameAndAddress(config)} Reason: ${e?.message??e}`)
|
|
355
|
-
app.debug(e)
|
|
356
|
-
reject( e?.message??e )
|
|
357
|
-
})})
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
async function startScanner(transport) {
|
|
361
|
-
|
|
362
|
-
app.debug("Starting scan...");
|
|
363
|
-
//Use adapter.helper directly to get around Adapter::startDiscovery()
|
|
364
|
-
//filter options which can cause issues with Device::Connect()
|
|
365
|
-
//turning off Discovery
|
|
366
|
-
//try {await adapter.startDiscovery()}
|
|
367
|
-
try{
|
|
368
|
-
if (transport) {
|
|
369
|
-
app.debug(`Setting Bluetooth transport option to ${transport}`)
|
|
370
|
-
await adapter.helper.callMethod('SetDiscoveryFilter', {
|
|
371
|
-
Transport: new Variant('s', transport)
|
|
372
|
-
})
|
|
373
|
-
}
|
|
374
|
-
await adapter.helper.callMethod('StartDiscovery')
|
|
375
|
-
}
|
|
376
|
-
catch (error){
|
|
377
|
-
app.debug(error)
|
|
302
|
+
|
|
378
303
|
}
|
|
379
304
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
plugin.started=false
|
|
305
|
+
function updateSensor(sensor){
|
|
306
|
+
channel.broadcast(getSensorInfo(sensor), "sensorchanged")
|
|
307
|
+
}
|
|
385
308
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
plugin.start = async function (options, restartPlugin) {
|
|
391
|
-
plugin.started=true
|
|
309
|
+
function removeSensorFromList(sensor){
|
|
310
|
+
sensorMap.delete(sensor.getMacAddress())
|
|
311
|
+
channel.broadcast({mac:sensor.getMacAddress()},"removesensor")
|
|
312
|
+
}
|
|
392
313
|
|
|
393
|
-
|
|
314
|
+
function addSensorToList(sensor){
|
|
315
|
+
app.debug(`adding sensor to list ${sensor.getMacAddress()}`)
|
|
316
|
+
if (sensorMap.has(sensor.getMacAddress()) )
|
|
317
|
+
debugger
|
|
318
|
+
sensorMap.set(sensor.getMacAddress(),sensor)
|
|
319
|
+
channel.broadcast(sensorToJSON(sensor),"newsensor");
|
|
320
|
+
}
|
|
321
|
+
function deviceNameAndAddress(config){
|
|
322
|
+
return `${config?.name??""}${config.name?" at ":""}${config.mac_address}`
|
|
323
|
+
}
|
|
394
324
|
|
|
325
|
+
function createSensor(adapter, config) {
|
|
326
|
+
return new Promise( ( resolve, reject )=>{
|
|
327
|
+
var s
|
|
328
|
+
const startNumber=starts
|
|
329
|
+
//app.debug(`Waiting on ${deviceNameAndAddress(config)}`)
|
|
330
|
+
adapter.waitDevice(config.mac_address,(config?.discoveryTimeout??30)*1000)
|
|
331
|
+
.then(async (device)=> {
|
|
332
|
+
if (startNumber != starts ) {
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
//app.debug(`Found ${config.mac_address}`)
|
|
336
|
+
s = await instantiateSensor(device,config)
|
|
337
|
+
|
|
338
|
+
if (s instanceof BLACKLISTED)
|
|
339
|
+
reject ( `Device is blacklisted (${s.reasonForBlacklisting()}).`)
|
|
340
|
+
else{
|
|
341
|
+
addSensorToList(s)
|
|
342
|
+
s._lastRSSI=-1*Infinity
|
|
343
|
+
s.on("RSSI",(()=>{
|
|
344
|
+
if (Date.now()-s._lastRSSI > 20000) { //only update RSSI on client every five seconds
|
|
345
|
+
//app.debug(`${s.getMacAddress()} ${Date.now()-s._lastRSSI}`)
|
|
346
|
+
s._lastRSSI=Date.now()
|
|
347
|
+
updateSensor(s)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
}))
|
|
351
|
+
resolve(s)
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
.catch((e)=>{
|
|
355
|
+
if (s)
|
|
356
|
+
s.stopListening()
|
|
357
|
+
if (startNumber == starts ) {
|
|
358
|
+
app.debug(`Unable to communicate with device ${deviceNameAndAddress(config)} Reason: ${e?.message??e}`)
|
|
359
|
+
app.debug(e)
|
|
360
|
+
reject( e?.message??e )
|
|
361
|
+
}
|
|
362
|
+
})})
|
|
363
|
+
}
|
|
395
364
|
function getDeviceConfig(mac){
|
|
396
365
|
return deviceConfigs.find((p)=>p.mac_address==mac)
|
|
397
366
|
}
|
|
367
|
+
async function instantiateSensor(device,config){
|
|
368
|
+
try{
|
|
369
|
+
for (var [clsName, cls] of classMap) {
|
|
370
|
+
if (clsName.startsWith("_")) continue
|
|
371
|
+
const c = await cls.identify(device)
|
|
372
|
+
if (c) {
|
|
373
|
+
c.debug=app.debug
|
|
374
|
+
const sensor = new c(device,config?.params, config?.gattParams)
|
|
375
|
+
sensor.debug=app.debug
|
|
376
|
+
sensor.app=app
|
|
377
|
+
sensor._adapter=adapter //HACK!
|
|
378
|
+
|
|
379
|
+
await sensor.init()
|
|
380
|
+
//app.debug(`instantiated ${await BTSensor.getDeviceProp(device,"Address")}`)
|
|
381
|
+
|
|
382
|
+
return sensor
|
|
383
|
+
}
|
|
384
|
+
}} catch(error){
|
|
385
|
+
const msg = `Unable to instantiate ${await BTSensor.getDeviceProp(device,"Address")}: ${error.message} `
|
|
386
|
+
app.debug(msg)
|
|
387
|
+
app.debug(error)
|
|
388
|
+
app.setPluginError(msg)
|
|
389
|
+
}
|
|
390
|
+
//if we're here ain't got no class for the device
|
|
391
|
+
var sensor
|
|
392
|
+
if (config.params?.sensorClass){
|
|
393
|
+
var c = classMap.get(config.params.sensorClass)
|
|
394
|
+
} else{
|
|
395
|
+
c = classMap.get('UNKNOWN')
|
|
396
|
+
}
|
|
397
|
+
c.debug=app.debug
|
|
398
|
+
sensor = new c(device,config?.params, config?.gattParams)
|
|
399
|
+
sensor.debug=app.debug
|
|
400
|
+
sensor.app=app
|
|
398
401
|
|
|
402
|
+
await sensor.init()
|
|
403
|
+
return sensor
|
|
404
|
+
}
|
|
405
|
+
|
|
399
406
|
function initConfiguredDevice(deviceConfig){
|
|
407
|
+
const startNumber=starts
|
|
400
408
|
app.setPluginStatus(`Initializing ${deviceNameAndAddress(deviceConfig)}`);
|
|
401
409
|
if (!deviceConfig.discoveryTimeout)
|
|
402
410
|
deviceConfig.discoveryTimeout = options.discoveryTimeout
|
|
403
411
|
createSensor(adapter, deviceConfig).then((sensor)=>{
|
|
404
|
-
|
|
412
|
+
if (startNumber != starts ) {
|
|
413
|
+
return
|
|
414
|
+
}
|
|
405
415
|
if (deviceConfig.active) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
initPaths(deviceConfig)
|
|
409
|
-
}
|
|
410
|
-
const result = Promise.resolve(deviceConfig.sensor.listen())
|
|
411
|
-
result.then(() => {
|
|
412
|
-
app.debug(`Listening for changes from ${deviceConfig.sensor.getDisplayName()}`);
|
|
413
|
-
app.setPluginStatus(`Initial scan complete. Listening to ${++found} sensors.`);
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
+
app.setPluginStatus(`Listening to ${++foundConfiguredDevices} sensors.`);
|
|
417
|
+
sensor.activate(deviceConfig, plugin)
|
|
416
418
|
}
|
|
417
|
-
|
|
419
|
+
|
|
418
420
|
})
|
|
419
421
|
.catch((error)=>
|
|
420
422
|
{
|
|
423
|
+
if (deviceConfig.unconfigured) return
|
|
424
|
+
if (startNumber != starts ) {
|
|
425
|
+
return
|
|
426
|
+
}
|
|
421
427
|
const msg =`Sensor at ${deviceConfig.mac_address} unavailable. Reason: ${error}`
|
|
422
428
|
app.debug(msg)
|
|
423
429
|
app.debug(error)
|
|
424
430
|
if (deviceConfig.active)
|
|
425
431
|
app.setPluginError(msg)
|
|
426
|
-
|
|
427
|
-
|
|
432
|
+
const sensor=new MissingSensor(deviceConfig)
|
|
433
|
+
++foundConfiguredDevices
|
|
434
|
+
|
|
435
|
+
addSensorToList(sensor) //add sensor to list with known options
|
|
428
436
|
|
|
429
437
|
})
|
|
430
438
|
}
|
|
431
439
|
function findDevices (discoveryTimeout) {
|
|
440
|
+
const startNumber = starts
|
|
432
441
|
app.setPluginStatus("Scanning for new Bluetooth devices...");
|
|
433
442
|
|
|
434
443
|
adapter.devices().then( (macs)=>{
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
createSensor(adapter,
|
|
447
|
-
{mac_address:_mac, discoveryTimeout: discoveryTimeout*1000})
|
|
448
|
-
.then((s)=>
|
|
449
|
-
app.setPluginStatus(`Found ${s.getDisplayName()}.`))
|
|
450
|
-
.catch((e)=>
|
|
451
|
-
app.debug(`Device at ${_mac} unavailable. Reason: ${e}`))
|
|
444
|
+
if (startNumber != starts ) {
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
for (const mac of macs) {
|
|
448
|
+
var deviceConfig = getDeviceConfig(mac)
|
|
449
|
+
const sensor = sensorMap.get(mac)
|
|
450
|
+
|
|
451
|
+
if (sensor) {
|
|
452
|
+
if (sensor instanceof MissingSensor){
|
|
453
|
+
removeSensorFromList(sensor)
|
|
454
|
+
initConfiguredDevice(deviceConfig)
|
|
452
455
|
}
|
|
456
|
+
} else {
|
|
457
|
+
|
|
458
|
+
if (!deviceConfig) {
|
|
459
|
+
deviceConfig = {mac_address: mac,
|
|
460
|
+
discoveryTimeout: discoveryTimeout*1000,
|
|
461
|
+
active: false, unconfigured: true}
|
|
462
|
+
initConfiguredDevice(deviceConfig)
|
|
463
|
+
}
|
|
453
464
|
}
|
|
454
465
|
}
|
|
455
466
|
})
|
|
@@ -458,10 +469,13 @@ module.exports = function (app) {
|
|
|
458
469
|
function findDeviceLoop(discoveryTimeout, discoveryInterval, immediate=true ){
|
|
459
470
|
if (immediate)
|
|
460
471
|
findDevices(discoveryTimeout)
|
|
461
|
-
discoveryIntervalID =
|
|
472
|
+
discoveryIntervalID =
|
|
473
|
+
setInterval( findDevices, discoveryInterval*1000, discoveryTimeout)
|
|
462
474
|
}
|
|
463
|
-
|
|
464
|
-
|
|
475
|
+
|
|
476
|
+
channel.broadcast({state:"started"},"pluginstate")
|
|
477
|
+
|
|
478
|
+
|
|
465
479
|
if (!adapterID || adapterID=="")
|
|
466
480
|
adapterID = "hci0"
|
|
467
481
|
//Check if Adapter has changed since last start()
|
|
@@ -483,39 +497,32 @@ module.exports = function (app) {
|
|
|
483
497
|
|
|
484
498
|
await adapter.helper._prepare()
|
|
485
499
|
adapter.helper._propsProxy.on('PropertiesChanged', async (iface,changedProps,invalidated) => {
|
|
500
|
+
app.debug(changedProps)
|
|
486
501
|
if (Object.hasOwn(changedProps,"Powered")){
|
|
487
502
|
if (changedProps.Powered.value==false) {
|
|
488
503
|
if (plugin.started){ //only call stop() if plugin is started
|
|
489
504
|
app.setPluginStatus(`Bluetooth Adapter ${adapterID} turned off. Plugin disabled.`)
|
|
490
505
|
await plugin.stop()
|
|
491
|
-
adapterPower=false
|
|
492
506
|
}
|
|
493
507
|
} else {
|
|
494
|
-
|
|
495
|
-
adapterPower=true
|
|
496
|
-
await plugin.start(options,restartPlugin)
|
|
497
|
-
}
|
|
508
|
+
await restartPlugin(options)
|
|
498
509
|
}
|
|
499
510
|
}
|
|
500
511
|
})
|
|
501
512
|
if (!await adapter.isPowered()) {
|
|
502
513
|
app.debug(`Bluetooth Adapter ${adapterID} not powered on.`)
|
|
503
514
|
app.setPluginError(`Bluetooth Adapter ${adapterID} not powered on.`)
|
|
504
|
-
adapterPower=false
|
|
505
515
|
await plugin.stop()
|
|
506
516
|
return
|
|
507
517
|
}
|
|
508
518
|
}
|
|
509
|
-
adapterPower=true
|
|
510
519
|
|
|
511
|
-
plugin.uiSchema.peripherals['ui:disabled']=false
|
|
512
520
|
sensorMap.clear()
|
|
521
|
+
if (channel)
|
|
522
|
+
channel.broadcast({state:"started"},"pluginstate")
|
|
513
523
|
deviceConfigs=options?.peripherals??[]
|
|
514
524
|
|
|
515
525
|
if (plugin.stopped) {
|
|
516
|
-
await sleep(5000) //Make sure plugin.stop() completes first
|
|
517
|
-
//plugin.start is called asynchronously for some reason
|
|
518
|
-
//and does not wait for plugin.stop to complete
|
|
519
526
|
plugin.stopped=false
|
|
520
527
|
}
|
|
521
528
|
|
|
@@ -526,15 +533,10 @@ module.exports = function (app) {
|
|
|
526
533
|
plugin.schema.properties.adapter.enum.push(a.adapter)
|
|
527
534
|
plugin.schema.properties.adapter.enumNames.push(`${a.adapter} @ ${ await a.getAddress()} (${await a.getName()})`)
|
|
528
535
|
}
|
|
529
|
-
|
|
530
|
-
plugin.uiSchema.adapter={'ui:disabled': (activeAdapters.length==1)}
|
|
531
|
-
|
|
532
536
|
|
|
533
537
|
await startScanner(options.transport)
|
|
534
538
|
if (starts>0){
|
|
535
539
|
app.debug(`Plugin ${packageInfo.version} restarting...`);
|
|
536
|
-
if (plugin.schema.properties.peripherals.items.dependencies)
|
|
537
|
-
plugin.schema.properties.peripherals.items.dependencies.mac_address.oneOf=[]
|
|
538
540
|
} else {
|
|
539
541
|
app.debug(`Plugin ${packageInfo.version} started` )
|
|
540
542
|
|
|
@@ -542,43 +544,90 @@ module.exports = function (app) {
|
|
|
542
544
|
starts++
|
|
543
545
|
if (!await adapter.isDiscovering())
|
|
544
546
|
try{
|
|
545
|
-
await
|
|
547
|
+
await startScanner()
|
|
546
548
|
} catch (e){
|
|
547
549
|
app.debug(`Error starting scan: ${e.message}`)
|
|
548
550
|
}
|
|
549
551
|
if (!(deviceConfigs===undefined)){
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
552
|
+
const maxTimeout=Math.max(...deviceConfigs.map((dc)=>dc?.discoveryTimeout??options.discoveryTimeout))
|
|
553
|
+
var progress = 0
|
|
554
|
+
if (progressID==null)
|
|
555
|
+
progressID = setInterval(()=>{
|
|
556
|
+
channel.broadcast({"progress":++progress, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": deviceConfigs.length},"progress")
|
|
557
|
+
if ( foundConfiguredDevices==deviceConfigs.length){
|
|
558
|
+
app.debug("progress complete")
|
|
559
|
+
progressID,progressTimeoutID = null
|
|
560
|
+
clearTimeout(progressTimeoutID)
|
|
561
|
+
clearInterval(progressID)
|
|
562
|
+
progressID = null
|
|
563
|
+
}
|
|
564
|
+
},1000);
|
|
565
|
+
if (progressTimeoutID==null)
|
|
566
|
+
progressTimeoutID = setTimeout(()=> {
|
|
567
|
+
app.debug("progress timed out ")
|
|
568
|
+
if (progressID) {
|
|
569
|
+
|
|
570
|
+
clearInterval(progressID);
|
|
571
|
+
progressID=null
|
|
572
|
+
channel.broadcast({"progress":maxTimeout, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": deviceConfigs.length},"progress")
|
|
573
|
+
}
|
|
574
|
+
}, (maxTimeout+1)*1000);
|
|
575
|
+
|
|
576
|
+
for (const config of deviceConfigs) {
|
|
577
|
+
initConfiguredDevice(config)
|
|
553
578
|
}
|
|
554
579
|
}
|
|
580
|
+
const minTimeout=Math.min(...deviceConfigs.map((dc)=>dc?.discoveryTimeout??options.discoveryTimeout))
|
|
581
|
+
const intervalTimeout = ((minTimeout==Infinity)?(options?.discoveryTimeout??plugin.schema.properties.discoveryTimeout.default):minTimeout)*1000
|
|
582
|
+
deviceHealthID = setInterval( ()=> {
|
|
583
|
+
sensorMap.forEach((sensor)=>{
|
|
584
|
+
const config = getDeviceConfig(sensor.getMacAddress())
|
|
585
|
+
const dt = config?.discoveryTimeout??options.discoveryTimeout
|
|
586
|
+
const lc=sensor.elapsedTimeSinceLastContact()
|
|
587
|
+
if (lc > dt) {
|
|
588
|
+
app.debug(`${sensor.getMacAddress()} not heard from in ${lc} seconds`)
|
|
589
|
+
channel.broadcast(getSensorInfo(sensor), "sensorchanged")
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
}, intervalTimeout)
|
|
593
|
+
|
|
594
|
+
if (!options.hasOwnProperty("discoveryInterval" )) //no config -- first run
|
|
595
|
+
options.discoveryInterval = plugin.schema.properties.discoveryInterval.default
|
|
596
|
+
|
|
555
597
|
if (options.discoveryInterval && !discoveryIntervalID)
|
|
556
|
-
findDeviceLoop(options.discoveryTimeout,
|
|
598
|
+
findDeviceLoop(options?.discoveryTimeout??plugin.schema.properties.discoveryTimeout.default,
|
|
599
|
+
options.discoveryInterval)
|
|
557
600
|
}
|
|
558
601
|
plugin.stop = async function () {
|
|
559
602
|
app.debug("Stopping plugin")
|
|
560
603
|
plugin.stopped=true
|
|
561
604
|
plugin.started=false
|
|
562
|
-
|
|
605
|
+
channel.broadcast({state:"stopped"},"pluginstate")
|
|
606
|
+
if (discoveryIntervalID) {
|
|
607
|
+
clearInterval(discoveryIntervalID)
|
|
608
|
+
discoveryIntervalID=null
|
|
609
|
+
}
|
|
610
|
+
if (progressID) {
|
|
611
|
+
clearInterval(progressID)
|
|
612
|
+
progressID=null
|
|
613
|
+
}
|
|
614
|
+
if (progressTimeoutID) {
|
|
615
|
+
clearTimeout(progressTimeoutID)
|
|
616
|
+
progressTimeoutID=null
|
|
617
|
+
}
|
|
618
|
+
|
|
563
619
|
if ((sensorMap)){
|
|
564
|
-
|
|
565
|
-
plugin.schema.properties.peripherals.items.properties.mac_address.enumNames=[]
|
|
566
|
-
sensorMap.forEach(async (sensor, mac)=> {
|
|
620
|
+
for await (const sensorEntry of sensorMap.entries()) {
|
|
567
621
|
try{
|
|
568
|
-
await
|
|
569
|
-
app.debug(`No longer listening to ${
|
|
622
|
+
await sensorEntry[1].stopListening()
|
|
623
|
+
app.debug(`No longer listening to ${sensorEntry[0]}`)
|
|
570
624
|
}
|
|
571
625
|
catch (e){
|
|
572
|
-
app.debug(`Error stopping listening to ${
|
|
626
|
+
app.debug(`Error stopping listening to ${sensorEntry[0]}: ${e.message}`)
|
|
573
627
|
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if (discoveryIntervalID) {
|
|
578
|
-
clearInterval(discoveryIntervalID)
|
|
579
|
-
discoveryIntervalID=null
|
|
628
|
+
}
|
|
580
629
|
}
|
|
581
|
-
|
|
630
|
+
sensorMap.clear()
|
|
582
631
|
if (adapter && await adapter.isDiscovering())
|
|
583
632
|
try{
|
|
584
633
|
await adapter.stopDiscovery()
|
|
@@ -586,9 +635,10 @@ module.exports = function (app) {
|
|
|
586
635
|
} catch (e){
|
|
587
636
|
app.debug(`Error stopping scan: ${e.message}`)
|
|
588
637
|
}
|
|
638
|
+
|
|
589
639
|
app.debug('BT Sensors plugin stopped')
|
|
590
640
|
|
|
591
641
|
}
|
|
592
|
-
|
|
642
|
+
|
|
593
643
|
return plugin;
|
|
594
644
|
}
|