bt-sensors-plugin-sk 1.2.0-beta.0.1.5 → 1.2.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.
@@ -1,24 +1,8 @@
1
- module.exports = {
1
+ const Images = require('./VictronImages.js')
2
+
3
+ const VC=
4
+ {
2
5
 
3
- /*Device State?
4
- 0x00: Off
5
- 0x01: Low Power Mode,
6
- 0x02: Fault,
7
- 0x03: Bulk,
8
- 0x04: Absorption,
9
- 0x05: Float,
10
- 0x06: Storage,
11
- 0x07: Equalize,
12
- 0x08: Passthru,
13
- 0x09: Inverting,
14
- 0x0A: Assisting,
15
- 0x0B: Power Supply Mode,
16
- 0x0C-0xFA: Reserved,
17
- 0xFB: Test,
18
- 0xFC: Hub-1,
19
- 0xFD-0xFE: Reserved,
20
- 0xFF: Not Available
21
- */
22
6
  OperationMode: new Map([
23
7
  [0, 'OFF'],
24
8
  [1, 'LOW_POWER'],
@@ -73,9 +57,9 @@ module.exports = {
73
57
  [8, 'WATER_HEATER']
74
58
  ]),
75
59
  MODEL_ID_MAP :{
76
- 0x203: "BMV-700",
77
- 0x204: "BMV-702",
78
- 0x205: "BMV-700H",
60
+ 0x203: {name: "BMV-700",image: Images.bmv},
61
+ 0x204: {name: "BMV-702",image: Images.bmv},
62
+ 0x205: {name: "BMV-700H",image: Images.bmv},
79
63
  0x0300: "BlueSolar MPPT 70|15",
80
64
  0xA040: "BlueSolar MPPT 75|50",
81
65
  0xA041: "BlueSolar MPPT 150|35",
@@ -207,12 +191,12 @@ module.exports = {
207
191
  0xA346: "Phoenix Smart IP43 Charger 24|16 (1+1)",
208
192
  0xA347: "Phoenix Smart IP43 Charger 24|16 (3)",
209
193
  0xA350: "Phoenix Smart IP43 Charger 12|50 (1+1) 120-240V A350",
210
- 0xA381: "BMV-712 Smart",
211
- 0xA382: "BMV-710H Smart",
212
- 0xA383: "BMV-712 Smart Rev2",
213
- 0xA389: "SmartShunt 500A/50mV",
214
- 0xA38A: "SmartShunt 1000A/50mV",
215
- 0xA38B: "SmartShunt 2000A/50mV",
194
+ 0xA381: {name: "BMV-712 Smart", image: Images.bmv},
195
+ 0xA382: {name: "BMV-710H Smart", image: Images.bmv},
196
+ 0xA383: {name: "BMV-712 Smart Rev2", image: Images.bmv},
197
+ 0xA389: {name:"SmartShunt 500A/50mV",image: Images.shunt},
198
+ 0xA38A: {name:"SmartShunt 1000A/50mV",image: Images.shunt},
199
+ 0xA38B: {name:"SmartShunt 2000A/50mV",image: Images.shunt},
216
200
  0xA3A4: "Smart Battery Sense",
217
201
  0xA3A5: "Smart Battery Sense",
218
202
  0xA3B0: "Smart Battery Protect",
@@ -239,7 +223,7 @@ module.exports = {
239
223
  0xA3E6: "Lynx Smart BMS 1000",
240
224
  0x2780: "Victron Multiplus II 12/3000/120-50 2x120V",
241
225
  0xC00A: "Cerbo GX",
242
- 0xC030: "SmartShunt IP65 500A/50mV",
226
+ 0xC030: {name:"SmartShunt IP65 500A/50mV",image: Images.shunt},
243
227
  0xeba0: "Smart Lithium Battery",
244
228
 
245
229
  },
@@ -249,6 +233,7 @@ AuxMode:{
249
233
  TEMPERATURE: 2,
250
234
  DISABLED: 3
251
235
  },
236
+
252
237
  ChargerError:new Map()
253
238
  .set(0,"NO_ERROR")
254
239
  .set(1,"TEMPERATURE_BATTERY_HIGH")
@@ -330,6 +315,6 @@ OffReasons : new Map([
330
315
  [0x00000080, 'ENGINE_SHUTDOWN'],
331
316
  [0x00000081, 'ENGINE_SHUTDOWN_AND_INPUT_VOLTAGE_LOCKOUT'],
332
317
  [0x00000100, 'ANALYSING_INPUT_VOLTAGE']
333
- ]),
334
-
318
+ ])
335
319
  }
320
+ module.exports = VC
@@ -0,0 +1,8 @@
1
+ Images= {
2
+ generic: "victron-min-1.jpg ",
3
+ shunt: "Victron-SmartShunt.jpg",
4
+ bmv: "BMV-712-Smart-Front.webp",
5
+ smartSolar: "smartsolarMPPT7515.png"
6
+ }
7
+ module.exports=Images
8
+
@@ -1,6 +1,8 @@
1
+ const VC = require( './VictronConstants.js');
2
+ const Images = require('./VictronImages.js')
3
+
1
4
  const BTSensor = require("../../BTSensor.js");
2
5
  const crypto = require('node:crypto');
3
- const VC = require('./VictronConstants.js');
4
6
  function sleep(x) {
5
7
  return new Promise((resolve) => {
6
8
  setTimeout(() => {
@@ -59,12 +61,21 @@ function sleep(x) {
59
61
  }
60
62
  )
61
63
  this.model_id=this.getManufacturerData(0x2e1)?.readUInt16LE(2)??"Unknown"
64
+ this._schema.title = this.getName()
62
65
  }
63
66
  alarmReason(alarmValue){
64
67
  return this.constructor.AlarmReason[alarmValue]
65
68
  }
66
69
  getModelName(){
67
- return VC?.MODEL_ID_MAP[this.model_id]??this.constructor.name+" (Model ID:"+this.model_id+")"
70
+ const m = VC.MODEL_ID_MAP[this.model_id]
71
+ if(m) {
72
+ if(typeof m == 'string' || m instanceof String ) {
73
+ return m
74
+ } else {
75
+ return m.name
76
+ }
77
+ }
78
+ return this.constructor.name+" (Model ID:"+this.model_id+")"
68
79
  }
69
80
 
70
81
  decrypt(data){
@@ -110,7 +121,20 @@ function sleep(x) {
110
121
  throw new Error( "GATT Connection unimplemented for "+this.getDisplayName())
111
122
  }
112
123
 
113
-
124
+ getImage(){
125
+ const m = VC.MODEL_ID_MAP[this.model_id]
126
+ if (m && m.image)
127
+ return m.image
128
+ else
129
+ return Images.generic
130
+ }
131
+
132
+ getDescription(){
133
+ //return `<img src="https://www.victronenergy.com/_next/image?url=https%3A%2F%2Fwww.victronenergy.com%2Fupload%2Fproducts%2FSmartShunt%2520500_nw.png&w=1080&q=70"" height="150" object-fit="cover" ></img>`
134
+
135
+
136
+ return `<img src="../bt-sensors-plugin-sk/images/${this.getImage()}" height="300" object-fit="cover" ></img>`
137
+ }
114
138
 
115
139
  }
116
140
  module.exports=VictronSensor
@@ -195,6 +195,10 @@ class VictronBatteryMonitor extends VictronSensor{
195
195
  return "To use the GATT connection the SignalK server computer and the Smart Shunt must first be paired."
196
196
  }
197
197
 
198
+ getDescription(){
199
+ return super.getDescription()
200
+ }
201
+
198
202
  async stopListening(){
199
203
  super.stopListening()
200
204
  for (var c of this.characteristics){
@@ -39,5 +39,6 @@ class VictronSolarCharger extends VictronSensor{
39
39
  .default="electrical.solar.{id}.loadCurrent"
40
40
 
41
41
  }
42
+
42
43
  }
43
44
  module.exports=VictronSolarCharger
@@ -97,6 +97,11 @@ class XiaomiMiBeacon extends BTSensor{
97
97
  this.emit("humidity", buffer.readUInt8(2)/100)
98
98
  this.emitData("voltage",buffer,3);
99
99
  }
100
+
101
+ getDescription(){
102
+ return `<div><p><img src="../bt-sensors-plugin-sk/images/LYWSD03MMC-Device.jpg" alt=LYWSD03MMC image" style="float: left; margin-right: 10px;" /> The LYWSD03MMC temperature and humidity sensor is an inexpensive environmental sensor from Xiaomi Inc. <p> WARNING: If you use the GATT connection, you won't need an encrypytion//bind key to get your data but the energy cost of maintaining a GATT connection is high and will drain your battery in about 2 weeks of persistent use. Instead follow the instructions <a href=https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor/?tab=readme-ov-file#linux--home-assistant-in-ssh--web-terminal target="_blank">here</a> to get your device's encryption key.<div>`
103
+ }
104
+
100
105
  getManufacturer(){
101
106
  return "Xiaomi Inc."
102
107
  }
@@ -1,9 +1,12 @@
1
1
  import Form from '@rjsf/core' ;
2
2
  import validator from '@rjsf/validator-ajv8';
3
- import React from 'react'
3
+ import React from "react";
4
+ import ReactHtmlParser from 'react-html-parser';
5
+
4
6
  import {useEffect, useState} from 'react'
5
7
 
6
- import {Button, Grid} from '@material-ui/core';
8
+ import {Button, Grid } from '@material-ui/core';
9
+ import { makeStyles } from '@material-ui/core/styles';
7
10
 
8
11
  import { SignalCellular0Bar, SignalCellular1Bar, SignalCellular2Bar, SignalCellular3Bar, SignalCellular4Bar, SignalCellularConnectedNoInternet0Bar } from '@material-ui/icons';
9
12
 
@@ -12,30 +15,62 @@ const log = (type) => console.log.bind(console, type);
12
15
  import ListGroup from 'react-bootstrap/ListGroup';
13
16
  import Tabs from 'react-bootstrap/Tabs';
14
17
  import Tab from 'react-bootstrap/Tab';
18
+
15
19
  import { ListGroupItem } from 'react-bootstrap';
16
20
 
17
21
  import ProgressBar from 'react-bootstrap/ProgressBar';
18
22
 
19
-
20
23
  var _sensorMap, _sensorDomains={}, _sensorList={}
21
24
 
22
- export default (props) => {
25
+ export default function BTConfig (props) {
23
26
 
24
- const hideSubmit= {
25
- 'ui:submitButtonOptions': {
26
- props: {
27
- disabled: false,
28
- className: 'btn btn-info',
29
- },
30
- norender: true,
31
- submitText: 'Submit',
32
- },
27
+ const _uiSchema= {
28
+ "ui:options": {label: false},
29
+ 'title': { 'ui:widget': 'hidden' }
30
+ }
31
+
32
+ const baseUISchema =
33
+ {
34
+ "ui:field": "LayoutGridField",
35
+ "ui:layoutGrid": {
36
+ "ui:row":[
37
+ {
38
+ "ui:row": {
39
+ "className": "row",
40
+ "children": [
41
+ {
42
+ "ui:columns": {
43
+ "className": "col-xs-4",
44
+ "children": [
45
+ "adapter",
46
+ "transport",
47
+ "duplicateData",
48
+ "discoveryTimeout",
49
+ "discoveryInterval"
50
+ ]
51
+ }
52
+ }
53
+ ]
54
+ }
55
+ }
56
+ ]
57
+ }
33
58
  }
59
+
60
+ const useStyles = makeStyles((theme) => ({
61
+ root: {
62
+ '& > *': {
63
+ margin: theme.spacing(1),
64
+ },
65
+ },
66
+ }));
67
+
34
68
  const [baseSchema, setBaseSchema] = useState({})
69
+
35
70
  const [baseData, setBaseData] = useState({})
36
71
 
37
72
  const [schema, setSchema] = useState({})
38
- const [ uiSchema, setUISchema] = useState(hideSubmit )
73
+ const [ uiSchema, setUISchema] = useState(_uiSchema )
39
74
  const [sensorList, setSensorList] = useState([])
40
75
 
41
76
  const [sensorData, setSensorData] = useState()
@@ -49,11 +84,12 @@ export default (props) => {
49
84
 
50
85
  const [pluginState, setPluginState ] = useState("unknown")
51
86
  const [error, setError ] = useState()
87
+ const classes = useStyles();
52
88
 
53
-
89
+
54
90
  function sendJSONData(cmd, data){
55
91
 
56
- console.log(`sending ${cmd}`)
92
+ console.log(`sending ${cmd}`)
57
93
  console.log(data)
58
94
  const headers = new Headers();
59
95
  headers.append("Content-Type", "application/json");
@@ -67,11 +103,19 @@ export default (props) => {
67
103
 
68
104
  async function fetchJSONData(path){
69
105
  console.log(`fetching ${path}`)
70
-
71
- return fetch(`/plugins/bt-sensors-plugin-sk/${path}`, {
72
- credentials: 'include'
73
- })
74
-
106
+ var result
107
+ try {
108
+ result = fetch(`/plugins/bt-sensors-plugin-sk/${path}`, {
109
+ credentials: 'include'
110
+ })
111
+ } catch (e) {
112
+ result=
113
+ {
114
+ status: 500,
115
+ statusText: e.toString()
116
+ }
117
+ }
118
+ return result
75
119
  }
76
120
 
77
121
  async function getSensors(){
@@ -82,6 +126,9 @@ export default (props) => {
82
126
  }
83
127
  const json = await response.json()
84
128
  console.log(json)
129
+ //for (let i=0;i<json.length;i++){
130
+ // json[i].schema.htmlDescription=<div>{ReactHtmlParser(json[i].schema.htmlDescription)}<p></p></div>
131
+ //}
85
132
  return json
86
133
 
87
134
  }
@@ -100,17 +147,18 @@ export default (props) => {
100
147
  console.log("getBaseData")
101
148
  const response = await fetchJSONData("getBaseData")
102
149
  if (response.status!=200){
103
- throw new Error(`Unable get base data: ${response.statusText} (${response.status}) `)
150
+ throw new Error(`Unable to get base data: ${response.statusText} (${response.status}) `)
104
151
  }
105
152
  const json = await response.json()
106
153
  console.log(json)
154
+ json.schema.htmlDescription=<div>{ReactHtmlParser(json.schema.htmlDescription)}<p></p></div>
107
155
  return json
108
156
  }
109
157
  async function getProgress(){
110
158
  console.log("getProgress")
111
159
  const response = await fetchJSONData("getProgress")
112
160
  if (response.status!=200){
113
- throw new Error(`Unable get progres: ${response.statusText} (${response.status}) `)
161
+ throw new Error(`Unable to get progress: ${response.statusText} (${response.status}) `)
114
162
  }
115
163
  const json = await response.json()
116
164
  console.log(json)
@@ -163,14 +211,13 @@ export default (props) => {
163
211
  sendJSONData("updateBaseData", data).then( (response )=>{
164
212
  if (response.status != 200) {
165
213
  setError(new Error(`Unable to update base data: ${response.statusText} (${response.status})`))
166
- } else {
214
+ } /*else {
167
215
  getProgress().then((json)=>{
168
216
  setProgress(json)
169
217
  }).catch((e)=>{
170
218
  setError(e)
171
219
  })
172
- refreshSensors()
173
- }
220
+ }*/
174
221
  })
175
222
  }
176
223
 
@@ -221,6 +268,7 @@ export default (props) => {
221
268
 
222
269
  if (_sensorMap.has(json.mac)) {
223
270
  let sensor = _sensorMap.get(json.mac)
271
+
224
272
  Object.assign(sensor.info, json )
225
273
  setSensorMap(new Map ( _sensorMap ))
226
274
  }
@@ -372,66 +420,72 @@ useEffect(()=>{
372
420
 
373
421
  return Object.keys(_sensorList).map((domain)=> {return getTab(domain)})
374
422
  }
423
+ // <div style={{paddingBottom: 20}} class="d-flex flex-wrap justify-content-start align-items-start">
424
+ // <div class="d-flex flex-column" >
425
+
375
426
  function getTab(key){
376
427
  var title = key.slice(key.charAt(0)==="_"?1:0)
377
428
 
378
429
  return <Tab eventKey={key} title={title.charAt(0).toUpperCase()+title.slice(1) }>
379
430
 
380
- <div style={{paddingBottom: 20}} class="d-flex flex-wrap justify-content-start align-items-start">
381
431
  <ListGroup style={{ maxHeight: '300px', overflowY: 'auto' }}>
382
432
  {getSensorList(key)}
383
433
  </ListGroup>
434
+
384
435
 
385
- <div style= {{ paddingLeft: 10, paddingTop: 10, display: (Object.keys(schema).length==0)? "none" :"" }} >
436
+ </Tab>
437
+ }
386
438
 
387
- <Form
388
- schema={schema}
389
- validator={validator}
390
- uiSchema={uiSchema}
391
- onChange={(e) => {
392
- const s = sensorMap.get(e.formData.mac_address)
393
- s._changesMade=true
394
- setSensorData(e.formData)
395
- }
396
- }
397
- onSubmit={({ formData }, e) => {
398
- console.log(formData)
399
- updateSensorData(formData)
400
- setSchema({})
401
- alert("Changes saved")
402
- }}
403
- onError={log('errors')}
404
- formData={sensorData}>
405
- <div>
406
- <Grid direction="row" style={{spacing:5}}>
407
- <Button type='submit' color="primary" variant="contained">Save</Button>
408
- <Button variant="contained" onClick={()=>{undoChanges(sensorData.mac_address)}}>Undo</Button>
409
- <Button variant="contained" color="secondary" onClick={(e)=>confirmDelete(sensorData.mac_address)}>Delete</Button>
439
+
440
+ function openInNewTab (url) {
441
+ window.open(url, "_blank", "noreferrer");
442
+ }
410
443
 
444
+ function CustomFieldTemplate(props) {
445
+ const { id, classNames, style, label, help, required, description, errors, children } = props;
446
+ return (
447
+ <div className={classNames} style={style}>
448
+ <Grid container xs={12} direction="row" spacing="1">
449
+ <Grid item xs={1} sm container direction="column">
450
+ <Grid item ><label htmlFor={id}>{label}{required ? '*' : null}</label></Grid>
451
+ <Grid item> {description}</Grid>
452
+ </Grid>
453
+ <Grid item>{children}</Grid>
411
454
  </Grid>
412
- </div>
413
- </Form>
414
-
455
+ {errors}
456
+ {help}
415
457
  </div>
416
- </div>
417
- </Tab>
418
- }
458
+ );
459
+ }
419
460
 
420
461
  if (pluginState=="stopped" || pluginState=="unknown")
421
- return (<h1 >Enable plugin to see configuration (if plugin is Enabled and you're seeing this message, restart SignalK)</h1>)
462
+ return (<h3>Enable plugin to see configuration</h3>)
422
463
  else
423
464
  return(
424
465
 
425
466
  <div>
467
+ <div className={classes.root}>
468
+
469
+ <Button variant="contained" onClick={()=>{openInNewTab("https://github.com/naugehyde/bt-sensors-plugin-sk/tree/1.2.0-beta#configuration")}}>Documentation</Button>
470
+ <Button variant="contained" onClick={()=>{openInNewTab("https://github.com/naugehyde/bt-sensors-plugin-sk/issues/new/choose")}}>Report Issue</Button>
471
+ <Button variant="contained" onClick={()=>{openInNewTab("https://discord.com/channels/1170433917761892493/1295425963466952725" )}}>Discord Thread</Button>
472
+ <p></p>
473
+ <p></p>
474
+ </div>
475
+ <hr style={{"width":"100%","height":"1px","color":"gray","background-color":"gray","text-align":"left","margin-left":0}}></hr>
476
+
426
477
  {error?<h2 style="color: red;">{error}</h2>:""}
427
478
  <Form
428
479
  schema={baseSchema}
429
480
  validator={validator}
481
+ uiSchema={baseUISchema}
430
482
  onChange={(e) => setBaseData(e.formData)}
431
483
  onSubmit={ ({ formData }, e) => { updateBaseData(formData); setSchema({}) } }
484
+
432
485
  onError={log('errors')}
433
486
  formData={baseData}
434
487
  />
488
+ <hr style={{"width":"100%","height":"1px","color":"gray","background-color":"gray","text-align":"left","margin-left":0}}></hr>
435
489
  <p></p>
436
490
  <p></p>
437
491
  { (progress.deviceCount<progress.totalDevices)?
@@ -439,16 +493,47 @@ useEffect(()=>{
439
493
  now={progress.progress}
440
494
  />:""
441
495
  }
442
- <h2>{`${sensorMap.size>0?"Bluetooth Devices - Select to configure" :""}`}</h2>
443
- <h2>{`${sensorMap.size>0?"(* = sensor has unsaved changes)" :""}`}</h2>
444
496
  <p></p>
445
497
  <Tabs
446
498
  defaultActiveKey="_configured"
447
499
  id="domain-tabs"
448
500
  className="mb-3"
501
+ onClick={()=>{setSchema({})}}
449
502
  >
450
503
  {getTabs()}
451
504
  </Tabs>
505
+ <div style= {{ paddingLeft: 10, paddingTop: 10, display: (Object.keys(schema).length==0)? "none" :"" }}>
506
+ <Grid container direction="column" style={{spacing:5}}>
507
+ <Grid item><h2>{schema?.title}</h2><p></p></Grid>
508
+ <Grid item>{ReactHtmlParser(schema?.htmlDescription)}</Grid>
509
+ </Grid>
510
+
511
+ <Form
512
+ schema={schema}
513
+ validator={validator}
514
+ uiSchema={uiSchema}
515
+ onChange={(e) => {
516
+ const s = sensorMap.get(e.formData.mac_address)
517
+ s._changesMade=true
518
+ setSensorData(e.formData)
519
+ }
520
+ }
521
+ onSubmit={({ formData }, e) => {
522
+ updateSensorData(formData)
523
+ setSchema({})
524
+ alert("Changes saved")
525
+ }}
526
+ onError={log('errors')}
527
+ formData={sensorData}>
528
+ <div className={classes.root}>
529
+ <Button type='submit' color="primary" variant="contained">Save</Button>
530
+ <Button variant="contained" onClick={()=>{undoChanges(sensorData.mac_address)}}>Undo</Button>
531
+ <Button variant="contained" color="secondary" onClick={(e)=>confirmDelete(sensorData.mac_address)}>Delete</Button>
532
+ </div>
533
+ </Form>
534
+
535
+
536
+ </div>
452
537
  </div>
453
538
  )
454
539
 
package/webpack.config.js CHANGED
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const { ModuleFederationPlugin } = require('webpack').container;
5
5
  const { WatchIgnorePlugin } = require('webpack')
6
6
 
7
- require('@signalk/server-admin-ui-dependencies')
7
+ //require('@signalk/server-admin-ui-dependencies')
8
8
 
9
9
  const packageJson = require('./package')
10
10