bt-sensors-plugin-sk 1.0.2 → 1.1.0-beta.1
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 +337 -40
- package/README.md +3 -3
- package/bt_co.json +14520 -0
- package/index.js +385 -177
- package/package.json +4 -3
- package/sensor_classes/ATC.js +31 -22
- package/sensor_classes/BlackListedDevice.js +20 -0
- package/sensor_classes/Inkbird.js +51 -0
- package/sensor_classes/RuuviTag.js +121 -0
- package/sensor_classes/UNKNOWN.js +12 -0
- package/sensor_classes/Victron/VictronConstants.js +328 -0
- package/sensor_classes/Victron/VictronSensor.js +106 -0
- package/sensor_classes/VictronACCharger.js +64 -0
- package/sensor_classes/VictronBatteryMonitor.js +160 -0
- package/sensor_classes/VictronDCDCConverter.js +26 -0
- package/sensor_classes/VictronDCEnergyMeter.js +68 -0
- package/sensor_classes/VictronGXDevice.js +43 -0
- package/sensor_classes/VictronInverter.js +44 -0
- package/sensor_classes/VictronInverterRS.js +44 -0
- package/sensor_classes/VictronLynxSmartBMS.js +50 -0
- package/sensor_classes/VictronOrionXS.js +31 -0
- package/sensor_classes/VictronSmartBatteryProtect.js +49 -0
- package/sensor_classes/VictronSmartLithium.js +66 -0
- package/sensor_classes/VictronSolarCharger.js +29 -0
- package/sensor_classes/VictronVEBus.js +47 -0
- package/sensor_classes/XiaomiMiBeacon.js +232 -0
- package/test.js +32 -0
- package/sensor_classes/LYWSD03MMC.js +0 -39
- package/sensor_classes/SmartShunt.js +0 -300
- package/sensor_classes/SmartShunt_GATT.js +0 -64
- package/sensor_classes/TPS.js +0 -49
package/index.js
CHANGED
|
@@ -5,20 +5,58 @@ const {createBluetooth} = require('node-ble')
|
|
|
5
5
|
const {bluetooth, destroy} = createBluetooth()
|
|
6
6
|
|
|
7
7
|
const BTSensor = require('./BTSensor.js')
|
|
8
|
+
const BLACKLISTED = require('./sensor_classes/BlackListedDevice.js')
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
class MissingSensor {
|
|
11
|
+
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
constructor(config){
|
|
14
|
+
this.Metadatum = BTSensor.Metadatum
|
|
15
|
+
this.addMetadatum=BTSensor.prototype.addMetadatum
|
|
16
|
+
this.getPathMetadata = BTSensor.prototype.getPathMetadata
|
|
17
|
+
this.getParamMetadata = BTSensor.prototype.getParamMetadata
|
|
14
18
|
|
|
19
|
+
this.metadata= new Map()
|
|
20
|
+
var keys = Object.keys(config?.paths??{})
|
|
21
|
+
this.addMetadatum.bind(this)
|
|
22
|
+
keys.forEach((key)=>{
|
|
23
|
+
this.addMetadatum(key, config.paths[key]?.type??'string', config.paths[key].description )
|
|
24
|
+
} )
|
|
25
|
+
keys = Object.keys(config?.params??{})
|
|
26
|
+
keys.forEach((key)=>{
|
|
27
|
+
this.addMetadatum(key, config.params[key]?.type??'string', config.params[key].description ).isParam=true
|
|
28
|
+
this[key]=config.params[key]
|
|
29
|
+
})
|
|
30
|
+
this.mac_address = config.mac_address
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
canUseGATT(){
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
getMetadata(){
|
|
37
|
+
return this.metadata
|
|
38
|
+
}
|
|
39
|
+
getMacAddress(){
|
|
40
|
+
return this.mac_address
|
|
41
|
+
}
|
|
42
|
+
getDescription(){
|
|
43
|
+
return ""
|
|
44
|
+
}
|
|
45
|
+
getName(){
|
|
46
|
+
return this?.name??"Unknown device"
|
|
47
|
+
}
|
|
48
|
+
getDisplayName(){
|
|
49
|
+
return `OUT OF RANGE DEVICE (${this.getName()} ${this.getMacAddress()})`
|
|
50
|
+
}
|
|
51
|
+
disconnect(){}
|
|
52
|
+
connect(){}
|
|
53
|
+
}
|
|
15
54
|
module.exports = function (app) {
|
|
16
|
-
const discoveryTimeout = 30
|
|
17
55
|
const adapterID = 'hci0'
|
|
18
|
-
|
|
19
|
-
var peripherals=[]
|
|
56
|
+
var deviceConfigs
|
|
20
57
|
var starts=0
|
|
21
58
|
var classMap
|
|
59
|
+
|
|
22
60
|
var utilities_sk
|
|
23
61
|
|
|
24
62
|
var plugin = {};
|
|
@@ -29,233 +67,403 @@ module.exports = function (app) {
|
|
|
29
67
|
//Try and load utilities-sk NOTE: should be installed from App Store--
|
|
30
68
|
//But there's a fail safe because I'm a reasonable man.
|
|
31
69
|
|
|
32
|
-
|
|
33
|
-
|
|
70
|
+
utilities_sk = {
|
|
71
|
+
loadClasses: function(dir, ext='.js')
|
|
72
|
+
{
|
|
73
|
+
const classMap = new Map()
|
|
74
|
+
const classFiles = fs.readdirSync(dir)
|
|
75
|
+
.filter(file => file.endsWith(ext));
|
|
76
|
+
|
|
77
|
+
classFiles.forEach(file => {
|
|
78
|
+
const filePath = path.join(dir, file);
|
|
79
|
+
const cls = require(filePath);
|
|
80
|
+
classMap.set(cls.name, cls);
|
|
81
|
+
})
|
|
82
|
+
return classMap
|
|
83
|
+
}
|
|
34
84
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
85
|
+
|
|
86
|
+
function sleep(x) {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
resolve(x);
|
|
90
|
+
}, x);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function instantiateSensor(device,config){
|
|
94
|
+
try{
|
|
95
|
+
for (var [clsName, cls] of classMap) {
|
|
96
|
+
const c = await cls.identify(device)
|
|
97
|
+
if (c) {
|
|
46
98
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
99
|
+
if (c.name.startsWith("_")) continue
|
|
100
|
+
c.debug=app.debug
|
|
101
|
+
c.debug.bind(c)
|
|
102
|
+
const sensor = new c(device,config?.params, config?.gattParams)
|
|
103
|
+
sensor.debug=app.debug
|
|
104
|
+
await sensor.init()
|
|
105
|
+
return sensor
|
|
106
|
+
}
|
|
107
|
+
}} catch(error){
|
|
108
|
+
const msg = `Unable to instantiate ${await BTSensor.getDeviceProp(device,"Address")}: ${error.message} `
|
|
109
|
+
app.debug(msg)
|
|
110
|
+
app.debug(error)
|
|
111
|
+
app.setPluginError(msg)
|
|
55
112
|
}
|
|
113
|
+
//if we're here ain't got no class for the device
|
|
114
|
+
const sensor = new (classMap.get('UNKNOWN'))(device)
|
|
115
|
+
await sensor.init()
|
|
116
|
+
return sensor
|
|
56
117
|
}
|
|
57
|
-
function createPaths(sensorClass, peripheral){
|
|
58
118
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
119
|
+
function createPaths(config){
|
|
120
|
+
config.sensor.getMetadata().forEach((metadatum, tag)=>{
|
|
121
|
+
if ((!(metadatum?.isParam)??false)){ //param metadata is passed to the sensor at
|
|
122
|
+
//create time through the constructor, and isn't a
|
|
123
|
+
//a value you want to see in a path
|
|
124
|
+
|
|
125
|
+
const path = config.paths[tag]
|
|
126
|
+
if (!(path===undefined))
|
|
127
|
+
app.handleMessage(plugin.id,
|
|
128
|
+
{
|
|
129
|
+
updates:
|
|
130
|
+
[{ meta: [{path: path, value: { units: metadatum?.unit }}]}]
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
70
135
|
|
|
136
|
+
function initPaths(deviceConfig){
|
|
137
|
+
deviceConfig.sensor.getMetadata().forEach((metadatum, tag)=>{
|
|
138
|
+
const path = deviceConfig.paths[tag];
|
|
139
|
+
if (!(path === undefined))
|
|
140
|
+
deviceConfig.sensor.on(tag, (val)=>{
|
|
141
|
+
if (metadatum.notify){
|
|
142
|
+
app.notify( tag, val, plugin.id )
|
|
143
|
+
} else {
|
|
144
|
+
updatePath(path,val)
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
}
|
|
71
149
|
function updatePath(path, val){
|
|
72
150
|
app.handleMessage(plugin.id, {updates: [ { values: [ {path: path, value: val }] } ] })
|
|
73
151
|
}
|
|
74
152
|
|
|
75
153
|
function loadClassMap() {
|
|
76
154
|
classMap = utilities_sk.loadClasses(path.join(__dirname, 'sensor_classes'))
|
|
77
|
-
|
|
78
155
|
}
|
|
79
156
|
|
|
80
157
|
app.debug('Loading plugin')
|
|
81
158
|
|
|
82
|
-
plugin.uiSchema = {
|
|
83
|
-
peripherals: {
|
|
84
|
-
'ui:disabled': true
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
159
|
plugin.schema = {
|
|
89
160
|
type: "object",
|
|
90
|
-
description: ""
|
|
161
|
+
description: "NOTE: \n 1) Plugin must be enabled to configure your sensors. \n"+
|
|
162
|
+
"2) You will have to wait until the scanner has found and connected to your device before seeing your device's config fields and saving the configuration. \n"+
|
|
163
|
+
"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"+
|
|
164
|
+
"4) If you submit and get errors it may be because the configured devices have not yet all been discovered.",
|
|
165
|
+
required:["discoveryTimeout", "discoveryInterval"],
|
|
91
166
|
properties: {
|
|
92
|
-
|
|
93
|
-
|
|
167
|
+
discoveryTimeout: {title: "Default device discovery timeout (in seconds)",
|
|
168
|
+
type: "integer", default: 30,
|
|
169
|
+
minimum: 10,
|
|
170
|
+
maximum: 3600
|
|
171
|
+
},
|
|
172
|
+
discoveryInterval: {title: "Scan for new devices interval (in seconds-- 0 for no new device scanning)",
|
|
173
|
+
type: "integer",
|
|
174
|
+
default: 60,
|
|
175
|
+
minimum: 0,
|
|
176
|
+
multipleOf: 10
|
|
177
|
+
},
|
|
94
178
|
peripherals:
|
|
95
179
|
{ type: "array", title: "Sensors", items:{
|
|
96
|
-
title: "", type: "object",
|
|
180
|
+
title: "", type: "object",
|
|
181
|
+
required:["mac_address", "discoveryTimeout"],
|
|
97
182
|
properties:{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
183
|
+
active: {title: "Active", type: "boolean", default: true },
|
|
184
|
+
mac_address: {title: "Bluetooth Sensor", type: "string" },
|
|
185
|
+
discoveryTimeout: {title: "Device discovery timeout (in seconds)",
|
|
186
|
+
type: "integer", default:30,
|
|
187
|
+
minimum: 10,
|
|
188
|
+
maximum: 600
|
|
189
|
+
}
|
|
190
|
+
}
|
|
103
191
|
|
|
104
192
|
}
|
|
105
193
|
}
|
|
106
194
|
}
|
|
107
195
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
196
|
+
const UI_SCHEMA =
|
|
197
|
+
{ "peripherals": {
|
|
198
|
+
'ui:disabled': !plugin.started
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
plugin.uiSchema=UI_SCHEMA
|
|
202
|
+
|
|
203
|
+
function updateSensorDisplayName(sensor){
|
|
204
|
+
const mac_address = sensor.getMacAddress()
|
|
205
|
+
const displayName = sensor.getDisplayName()
|
|
206
|
+
|
|
207
|
+
const mac_addresses_list = plugin.schema.properties.peripherals.items.properties.
|
|
113
208
|
mac_address.enum
|
|
209
|
+
const mac_addresses_names = plugin.schema.properties.peripherals.items.properties.
|
|
210
|
+
mac_address.enumNames
|
|
114
211
|
|
|
115
|
-
|
|
212
|
+
var index = mac_addresses_list.indexOf(mac_address)
|
|
213
|
+
if (index!=-1)
|
|
214
|
+
mac_addresses_names[index]= displayName
|
|
116
215
|
}
|
|
117
216
|
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
mac_address.
|
|
217
|
+
function removeSensorFromList(sensor){
|
|
218
|
+
const mac_addresses_list = plugin.schema.properties.peripherals.items.properties.mac_address.enum
|
|
219
|
+
const mac_addresses_names = plugin.schema.properties.peripherals.items.properties.mac_address.enumNames
|
|
220
|
+
const oneOf = plugin.schema.properties.peripherals.items.dependencies.mac_address.oneOf
|
|
221
|
+
const mac_address = sensor.getMacAddress()
|
|
222
|
+
|
|
223
|
+
const i = mac_addresses_list.indexOf(mac_address)
|
|
224
|
+
if (i<0) return // n'existe pas
|
|
225
|
+
|
|
226
|
+
mac_addresses_list.splice(i,1)
|
|
227
|
+
mac_addresses_names.splice(i,1)
|
|
228
|
+
oneOf.splice(oneOf.findIndex((p)=>p.properties.mac_address.enum[0]==mac_address),1)
|
|
121
229
|
|
|
122
|
-
if (!devices.includes(device)) {
|
|
123
|
-
devices.push(device)
|
|
124
|
-
if (!(name===undefined))
|
|
125
|
-
setDeviceNameInList(device,name)
|
|
126
|
-
}
|
|
127
230
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
231
|
+
function addSensorToList(sensor){
|
|
232
|
+
if (!plugin.schema.properties.peripherals.items.dependencies)
|
|
233
|
+
plugin.schema.properties.peripherals.items.dependencies={mac_address:{oneOf:[]}}
|
|
234
|
+
|
|
235
|
+
if(plugin.schema.properties.peripherals.items.properties.mac_address.enum==undefined) {
|
|
236
|
+
plugin.schema.properties.peripherals.items.properties.mac_address.enum=[]
|
|
237
|
+
plugin.schema.properties.peripherals.items.properties.mac_address.enumNames=[]
|
|
132
238
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
239
|
+
const mac_addresses_names = plugin.schema.properties.peripherals.items.properties.mac_address.enumNames
|
|
240
|
+
const mac_addresses_list = plugin.schema.properties.peripherals.items.properties.mac_address.enum
|
|
241
|
+
const mac_address = sensor.getMacAddress()
|
|
242
|
+
const displayName = sensor.getDisplayName()
|
|
136
243
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
244
|
+
var index = mac_addresses_list.indexOf(mac_address)
|
|
245
|
+
if (index==-1)
|
|
246
|
+
index = mac_addresses_list.push(mac_address)-1
|
|
247
|
+
mac_addresses_names[index]= displayName
|
|
248
|
+
var oneOf = {properties:{mac_address:{enum:[mac_address]}}}
|
|
249
|
+
|
|
250
|
+
oneOf.properties.params={
|
|
251
|
+
title:`Device parameters`,
|
|
252
|
+
description: sensor.getDescription(),
|
|
253
|
+
type:"object",
|
|
254
|
+
properties:{}
|
|
255
|
+
}
|
|
256
|
+
sensor.getParamMetadata().forEach((metadatum,tag)=>{
|
|
257
|
+
oneOf.properties.params.properties[tag]=metadatum.asJSONSchema()
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
if (sensor.canUseGATT()){
|
|
261
|
+
|
|
262
|
+
oneOf.properties.gattParams={
|
|
263
|
+
title:`GATT Specific device parameters`,
|
|
264
|
+
description: sensor.getGATTDescription(),
|
|
265
|
+
type:"object",
|
|
266
|
+
properties:{}
|
|
267
|
+
}
|
|
268
|
+
sensor.getGATTParamMetadata().forEach((metadatum,tag)=>{
|
|
269
|
+
oneOf.properties.gattParams.properties[tag]=metadatum.asJSONSchema()
|
|
149
270
|
})
|
|
271
|
+
}
|
|
150
272
|
|
|
151
|
-
|
|
273
|
+
oneOf.properties.paths={
|
|
274
|
+
title:"Signalk Paths",
|
|
275
|
+
description: `Signalk paths to be updated when ${sensor.getName()}'s values change`,
|
|
276
|
+
type:"object",
|
|
277
|
+
properties:{}
|
|
278
|
+
}
|
|
279
|
+
sensor.getPathMetadata().forEach((metadatum,tag)=>{
|
|
280
|
+
oneOf.properties.paths.properties[tag]=metadatum.asJSONSchema()
|
|
152
281
|
})
|
|
282
|
+
plugin.schema.properties.peripherals.items.dependencies.mac_address.oneOf.push(oneOf)
|
|
283
|
+
//plugin.schema.properties.peripherals.items.title=sensor.getName()
|
|
284
|
+
|
|
153
285
|
}
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
286
|
+
function deviceNameAndAddress(config){
|
|
287
|
+
return `${config?.name??""}${config.name?" at ":""}${config.mac_address}`
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function createSensor(adapter, config) {
|
|
291
|
+
return new Promise( ( resolve, reject )=>{
|
|
292
|
+
var s
|
|
293
|
+
|
|
294
|
+
app.debug(`Waiting on ${deviceNameAndAddress(config)}`)
|
|
295
|
+
adapter.waitDevice(config.mac_address,config.discoveryTimeout*1000)
|
|
296
|
+
.then(async (device)=> {
|
|
297
|
+
app.debug(`Found ${config.mac_address}`)
|
|
298
|
+
s = await instantiateSensor(device,config)
|
|
299
|
+
sensorMap.set(config.mac_address,s)
|
|
300
|
+
|
|
301
|
+
if (s instanceof BLACKLISTED)
|
|
302
|
+
reject ( `Device is blacklisted (${s.reasonForBlacklisting()}).`)
|
|
303
|
+
else{
|
|
304
|
+
addSensorToList(s)
|
|
305
|
+
s.on("RSSI",(()=>{
|
|
306
|
+
updateSensorDisplayName(s)
|
|
307
|
+
}))
|
|
308
|
+
resolve(s)
|
|
161
309
|
}
|
|
162
|
-
plugin.schema.description='Scanning for Bluetooth devices...'
|
|
163
|
-
setTimeout( () => {
|
|
164
|
-
adapter.devices().then((devices)=>{
|
|
165
|
-
app.debug(`Found: ${util.inspect(devices)}`)
|
|
166
|
-
devices.forEach( (device) => {
|
|
167
|
-
adapter.waitDevice(device,discoveryTimeout*1000).then((d)=>{
|
|
168
|
-
getDeviceName(d).then((dn)=>{
|
|
169
|
-
addDeviceToList(device, dn )
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
.catch ((e)=> {
|
|
173
|
-
app.debug(e)
|
|
174
|
-
})
|
|
175
|
-
})
|
|
176
|
-
plugin.schema.description=`Scan complete. Found ${devices.length} Bluetooth devices.`
|
|
177
|
-
})
|
|
178
|
-
plugin.uiSchema.peripherals['ui:disabled']=false
|
|
179
|
-
}, app.settings?.btDiscoveryTimeout ?? discoveryTimeout * 1000)
|
|
180
310
|
})
|
|
311
|
+
.catch((e)=>{
|
|
312
|
+
if (s)
|
|
313
|
+
s.disconnect()
|
|
314
|
+
app.debug(`Unable to communicate with device ${deviceNameAndAddress(config)} Reason: ${e?.message??e}`)
|
|
315
|
+
reject( e?.message??e )
|
|
316
|
+
})})
|
|
181
317
|
}
|
|
318
|
+
|
|
319
|
+
async function startScanner() {
|
|
320
|
+
|
|
321
|
+
app.debug("Starting scan...");
|
|
322
|
+
try{ await adapter.stopDiscovery() } catch(error){}
|
|
323
|
+
try{ await adapter.startDiscovery() } catch(error){}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const sensorMap=new Map()
|
|
327
|
+
var adapter
|
|
328
|
+
|
|
329
|
+
plugin.started=false
|
|
330
|
+
|
|
182
331
|
loadClassMap()
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
startScanner()
|
|
332
|
+
var discoveryIntervalID
|
|
333
|
+
|
|
186
334
|
plugin.start = async function (options, restartPlugin) {
|
|
187
|
-
if (starts>0){
|
|
188
|
-
app.debug('Plugin restarted');
|
|
189
|
-
plugin.uiSchema.peripherals['ui:disabled']=true
|
|
190
|
-
loadClassMap()
|
|
191
|
-
updateClassProperties()
|
|
192
|
-
startScanner()
|
|
193
|
-
} else {
|
|
194
|
-
app.debug('Plugin started');
|
|
195
|
-
}
|
|
196
|
-
starts++
|
|
197
|
-
const adapter = await bluetooth.getAdapter(options?.adapter??app.settings?.btAdapter??adapterID)
|
|
198
|
-
peripherals=options.peripherals
|
|
199
|
-
if (!(peripherals===undefined)){
|
|
200
|
-
var found = 0
|
|
201
|
-
for (const peripheral of peripherals) {
|
|
202
|
-
|
|
203
|
-
addDeviceToList(peripheral.mac_address)
|
|
204
|
-
app.setPluginStatus(`Waiting on ${peripheral.mac_address}`);
|
|
205
|
-
adapter.waitDevice(peripheral.mac_address,1000*peripheral.discoveryTimeout).then(async (device)=>
|
|
206
|
-
{
|
|
207
335
|
|
|
208
|
-
|
|
336
|
+
function getDeviceConfig(mac){
|
|
337
|
+
return deviceConfigs.find((p)=>p.mac_address==mac)
|
|
338
|
+
}
|
|
209
339
|
|
|
210
|
-
|
|
340
|
+
function initConfiguredDevice(deviceConfig){
|
|
341
|
+
app.setPluginStatus(`Initializing ${deviceNameAndAddress(deviceConfig)}`);
|
|
342
|
+
if (!deviceConfig.discoveryTimeout)
|
|
343
|
+
deviceConfig.discoveryTimeout = options.discoveryTimeout
|
|
344
|
+
createSensor(adapter, deviceConfig).then((sensor)=>{
|
|
345
|
+
deviceConfig.sensor=sensor
|
|
346
|
+
if (deviceConfig.active) {
|
|
347
|
+
createPaths(deviceConfig)
|
|
348
|
+
initPaths(deviceConfig)
|
|
349
|
+
const result = Promise.resolve(deviceConfig.sensor.connect())
|
|
350
|
+
result.then(() => {
|
|
351
|
+
app.debug(`Connected to ${deviceConfig.sensor.getDisplayName()}`);
|
|
352
|
+
app.setPluginStatus(`Initial scan complete. Connected to ${++found} sensors.`);
|
|
353
|
+
})
|
|
354
|
+
}
|
|
211
355
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
356
|
+
})
|
|
357
|
+
.catch((error)=>
|
|
358
|
+
{
|
|
359
|
+
const msg =`Sensor at ${deviceConfig.mac_address} unavailable. Reason: ${error}`
|
|
360
|
+
app.debug(msg)
|
|
361
|
+
app.debug(error)
|
|
362
|
+
app.setPluginError(msg)
|
|
363
|
+
deviceConfig.sensor=new MissingSensor(deviceConfig)
|
|
364
|
+
addSensorToList(deviceConfig.sensor) //add sensor to list with known options
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
function findDevices (discoveryTimeout) {
|
|
368
|
+
app.setPluginStatus("Scanning for new Bluetooth devices...");
|
|
369
|
+
|
|
370
|
+
adapter.devices().then( (macs)=>{
|
|
371
|
+
for (var mac of macs) {
|
|
372
|
+
const deviceConfig = getDeviceConfig(mac)
|
|
373
|
+
const _mac = mac
|
|
216
374
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
375
|
+
if (deviceConfig && deviceConfig.sensor instanceof MissingSensor){
|
|
376
|
+
removeSensorFromList(deviceConfig.sensor)
|
|
377
|
+
initConfiguredDevice(deviceConfig)
|
|
378
|
+
} else
|
|
379
|
+
{
|
|
380
|
+
if (!sensorMap.has(_mac)) {
|
|
381
|
+
if (deviceConfig) continue;
|
|
382
|
+
createSensor(adapter,
|
|
383
|
+
{mac_address:_mac, discoveryTimeout: discoveryTimeout*1000})
|
|
384
|
+
.then((s)=>
|
|
385
|
+
app.setPluginStatus(`Found ${s.getDisplayName()}.`))
|
|
386
|
+
.catch((e)=>
|
|
387
|
+
app.debug(`Device at ${_mac} unavailable. Reason: ${e}`))
|
|
225
388
|
}
|
|
226
|
-
app.debug('Device: '+peripheral.mac_address+' connected.')
|
|
227
|
-
app.setPluginStatus(`Connected to ${found++} sensors.`);
|
|
228
389
|
}
|
|
229
|
-
})
|
|
230
|
-
.catch ((e)=> {
|
|
231
|
-
if (peripheral.sensor)
|
|
232
|
-
peripheral.sensor.disconnect()
|
|
233
|
-
app.debug("Unable to connect to device " + peripheral.mac_address +". Reason: "+ e.message )
|
|
234
|
-
})
|
|
235
|
-
.finally( ()=>{
|
|
236
|
-
app.setPluginStatus(`Connected to ${found} sensors.`);
|
|
237
390
|
}
|
|
238
|
-
|
|
239
|
-
}
|
|
391
|
+
})
|
|
240
392
|
}
|
|
241
393
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
394
|
+
function findDeviceLoop(discoveryTimeout, discoveryInterval, immediate=true ){
|
|
395
|
+
if (immediate)
|
|
396
|
+
findDevices(discoveryTimeout)
|
|
397
|
+
discoveryIntervalID = setInterval( findDevices, discoveryInterval*1000, discoveryTimeout)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
plugin.started=true
|
|
401
|
+
plugin.uiSchema.peripherals['ui:disabled']=false
|
|
402
|
+
sensorMap.clear()
|
|
403
|
+
deviceConfigs=options?.peripherals??[]
|
|
245
404
|
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
405
|
+
if (plugin.stopped) {
|
|
406
|
+
await sleep(5000) //Make sure plugin.stop() completes first
|
|
407
|
+
//plugin.start is called asynchronously for some reason
|
|
408
|
+
//and does not wait for plugin.stop to complete
|
|
409
|
+
plugin.stopped=false
|
|
410
|
+
}
|
|
411
|
+
adapter = await bluetooth.getAdapter(app.settings?.btAdapter??adapterID)
|
|
412
|
+
await startScanner()
|
|
413
|
+
if (starts>0){
|
|
414
|
+
app.debug('Plugin restarting...');
|
|
415
|
+
if (plugin.schema.properties.peripherals.items.dependencies)
|
|
416
|
+
plugin.schema.properties.peripherals.items.dependencies.mac_address.oneOf=[]
|
|
417
|
+
} else {
|
|
418
|
+
app.debug('Plugin build 2024.10.08 started');
|
|
419
|
+
|
|
420
|
+
}
|
|
421
|
+
starts++
|
|
422
|
+
if (!await adapter.isDiscovering())
|
|
423
|
+
try{
|
|
424
|
+
await adapter.startDiscovery()
|
|
425
|
+
} catch (e){
|
|
426
|
+
app.debug(`Error starting scan: ${e.message}`)
|
|
427
|
+
}
|
|
428
|
+
if (!(deviceConfigs===undefined)){
|
|
429
|
+
var found = 0
|
|
430
|
+
for (const deviceConfig of deviceConfigs) {
|
|
431
|
+
initConfiguredDevice(deviceConfig)
|
|
249
432
|
}
|
|
250
433
|
}
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
434
|
+
if (options.discoveryInterval && !discoveryIntervalID)
|
|
435
|
+
findDeviceLoop(options.discoveryTimeout, options.discoveryInterval)
|
|
436
|
+
}
|
|
437
|
+
plugin.stop = async function () {
|
|
438
|
+
app.debug("Stopping plugin")
|
|
439
|
+
plugin.stopped=true
|
|
440
|
+
plugin.uiSchema.peripherals['ui:disabled']=true
|
|
441
|
+
if ((sensorMap)){
|
|
442
|
+
sensorMap.forEach(async (sensor, mac)=> {
|
|
443
|
+
try{
|
|
444
|
+
await sensor.disconnect()
|
|
445
|
+
app.debug(`Disconnected from ${mac}`)
|
|
446
|
+
}
|
|
447
|
+
catch (e){
|
|
448
|
+
app.debug(`Error disconnecting from ${mac}: ${e.message}`)
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (discoveryIntervalID) {
|
|
454
|
+
clearInterval(discoveryIntervalID)
|
|
455
|
+
discoveryIntervalID=null
|
|
256
456
|
}
|
|
257
|
-
|
|
258
|
-
|
|
457
|
+
|
|
458
|
+
if (adapter && await adapter.isDiscovering())
|
|
459
|
+
try{
|
|
460
|
+
await adapter.stopDiscovery()
|
|
461
|
+
app.debug('Scan stopped')
|
|
462
|
+
} catch (e){
|
|
463
|
+
app.debug(`Error stopping scan: ${e.message}`)
|
|
464
|
+
}
|
|
465
|
+
app.debug('BT Sensors plugin stopped')
|
|
466
|
+
|
|
259
467
|
}
|
|
260
468
|
|
|
261
469
|
return plugin;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bt-sensors-plugin-sk",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Bluetooth Sensors for Signalk",
|
|
3
|
+
"version": "1.1.0-beta.1",
|
|
4
|
+
"description": "Bluetooth Sensors for Signalk - Beta 20241012",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"dbus-next": "^0.10.2",
|
|
8
|
-
"node-ble": "^1.9.0"
|
|
8
|
+
"node-ble": "^1.9.0",
|
|
9
|
+
"int24":"^0.0.1"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
12
|
"test": "echo \"Error: no test specified\" && exit 1"
|