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.
- package/BTSensor.js +52 -17
- package/README.md +184 -78
- package/Screenshot 2025-06-12 at 9.33.57/342/200/257AM.png +0 -0
- package/index.js +44 -51
- package/package.json +7 -6
- package/plugin_defaults.json +14 -5
- package/public/159.js +2 -1
- package/public/681.js +14 -0
- package/public/{239.js.LICENSE.txt → 681.js.LICENSE.txt} +9 -0
- package/public/847.js +1 -0
- package/public/main.js +1 -1
- package/public/remoteEntry.js +1 -1
- package/sensor_classes/ATC.js +22 -1
- package/sensor_classes/BlackListedDevice.js +10 -7
- package/sensor_classes/GobiusCTankMeter.js +18 -1
- package/sensor_classes/JBDBMS.js +75 -51
- package/sensor_classes/RenogyRoverClient.js +0 -1
- package/sensor_classes/ShellySBMO003Z.js +4 -0
- package/sensor_classes/ShenzhenLiOnBMS.js +177 -0
- package/sensor_classes/Victron/VictronConstants.js +17 -32
- package/sensor_classes/Victron/VictronImages.js +8 -0
- package/sensor_classes/Victron/VictronSensor.js +27 -3
- package/sensor_classes/VictronBatteryMonitor.js +4 -0
- package/sensor_classes/VictronSolarCharger.js +1 -0
- package/sensor_classes/XiaomiMiBeacon.js +5 -0
- package/src/components/PluginConfigurationPanel.js +145 -60
- package/webpack.config.js +1 -1
- package/public/239.js +0 -14
- package/public/778.js +0 -2
- /package/public/{778.js.LICENSE.txt → 159.js.LICENSE.txt} +0 -0
|
@@ -10,6 +10,15 @@ object-assign
|
|
|
10
10
|
http://jedwatson.github.io/classnames
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/*!
|
|
14
|
+
* The buffer module from node.js, for the browser.
|
|
15
|
+
*
|
|
16
|
+
* @author Feross Aboukhadijeh <https://feross.org>
|
|
17
|
+
* @license MIT
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
21
|
+
|
|
13
22
|
/**
|
|
14
23
|
* @license React
|
|
15
24
|
* react-is.production.min.js
|
package/public/847.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(self.webpackChunkbt_sensors_plugin_sk=self.webpackChunkbt_sensors_plugin_sk||[]).push([[847],{9337:()=>{},62995:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>k});var a=n(73490),o=n(74810),s=n(86528),r=n.n(s),l=n(40334),c=n(27606),i=n(86452),u=n(82096),g=n(3768),d=n(71431),m=n(39676),f=n(47041),h=n(86038),p=n(95027),E=n(43540),w=n(38250),y=n(31008),v=n(20455),S=n(23399);const b=e=>console.log.bind(console,e);var A,D={};function k(e){const t=(0,u.A)((e=>({root:{"& > *":{margin:e.spacing(1)}}}))),[n,k]=(0,s.useState)({}),[C,_]=(0,s.useState)({}),[x,$]=(0,s.useState)({}),[T,N]=(0,s.useState)({"ui:options":{label:!1},title:{"ui:widget":"hidden"}}),[j,M]=(0,s.useState)([]),[O,U]=(0,s.useState)(),[L,J]=(0,s.useState)(new Map),[B,I]=(0,s.useState)({progress:0,maxTimeout:100,deviceCount:0,totalDevices:0}),[P,R]=(0,s.useState)("unknown"),[G,H]=(0,s.useState)(),K=t();function F(e,t){console.log(`sending ${e}`),console.log(t);const n=new Headers;return n.append("Content-Type","application/json"),fetch(`/plugins/bt-sensors-plugin-sk/${e}`,{credentials:"include",method:"POST",body:JSON.stringify(t),headers:n})}async function W(e){var t;console.log(`fetching ${e}`);try{t=fetch(`/plugins/bt-sensors-plugin-sk/${e}`,{credentials:"include"})}catch(e){t={status:500,statusText:e.toString()}}return t}function Y(e){return Object.keys(e.config).length>0}function q(e){const t=Y(e);return r().createElement(v.A,{action:!0,onClick:()=>{e.config.mac_address=e.info.mac,$(e.schema),U(e.config)}},r().createElement("div",{class:"d-flex justify-content-between align-items-center",style:t?{fontWeight:"normal"}:{fontStyle:"italic"}},`${e._changesMade?"*":""}${e.info.name} MAC: ${e.info.mac} RSSI: ${n=e.info.RSSI,null==n?NaN:n}`,r().createElement("div",{class:"d-flex justify-content-between "},function(e){return null==e.info.lastContactDelta||e.info.lastContactDelta>e.config.discoveryTimeout?r().createElement(g.A,null):e.info.signalStrength>80?r().createElement(d.A,null):e.info.signalStrength>60?r().createElement(m.A,null):e.info.signalStrength>40?r().createElement(f.A,null):e.info.signalStrength>20?r().createElement(h.A,null):r().createElement(p.A,null)}(e))));var n}function z(e){window.open(e,"_blank","noreferrer")}return(0,s.useEffect)((()=>{console.log("useEffect([])");let e=null;return W("getPluginState").then((async t=>{if(404==t.status)throw R("unknown"),new Error("unable to get plugin state");const n=await t.json();console.log("Setting up eventsource"),e=new EventSource("/plugins/bt-sensors-plugin-sk/sse",{withCredentials:!0}),R(n.state),await async function(){console.log("getDomains");const e=await W("getDomains");if(200!=e.status)throw new Error(`Unable get domain data: ${e.statusText} (${e.status}) `);const t=await e.json();return console.log(t),t}(),e.addEventListener("newsensor",(e=>{console.log("newsensor");let t=JSON.parse(e.data);A.has(t.info.mac)||(console.log(`New sensor: ${t.info.mac}`),J(new Map(A.set(t.info.mac,t))))})),e.addEventListener("sensorchanged",(e=>{let t=JSON.parse(e.data);if(console.log("sensorchanged"),console.log(t),A.has(t.mac)){let e=A.get(t.mac);Object.assign(e.info,t),J(new Map(A))}})),e.addEventListener("progress",(e=>{console.log("progress");const t=JSON.parse(e.data);I(t),console.log(t)})),e.addEventListener("pluginstate",(e=>{console.log("pluginstate");const t=JSON.parse(e.data);R(t.state)}))})).catch((e=>{H(e)})),()=>{console.log("Closing connection to SSE"),e.close()}}),[]),(0,s.useEffect)((()=>{console.log("useEffect([pluginState])"),"started"==P?(console.log("refreshing sensor map"),async function(){console.log("getSensors");const e=await W("getSensors");if(200!=e.status)throw new Error(`Unable get sensor data: ${e.statusText} (${e.status}) `);const t=await e.json();return console.log(t),t}().then((e=>{J(new Map(e.map((e=>[e.info.mac,e]))))})).catch((e=>{H(e)})),async function(){console.log("getBaseData");const e=await W("getBaseData");if(200!=e.status)throw new Error(`Unable to get base data: ${e.statusText} (${e.status}) `);const t=await e.json();return console.log(t),t.schema.htmlDescription=r().createElement("div",null,(0,l.Ay)(t.schema.htmlDescription),r().createElement("p",null)),t}().then((e=>{k(e.schema),_(e.data)})).catch((e=>{H(e)})),async function(){console.log("getProgress");const e=await W("getProgress");if(200!=e.status)throw new Error(`Unable to get progress: ${e.statusText} (${e.status}) `);const t=await e.json();return console.log(t),t}().then((e=>{I(e)})).catch((e=>{H(e)}))):(J(new Map),k({}),_({}))}),[P]),(0,s.useEffect)((()=>{console.log("useEffect([sensorMap])"),A=L;const e=new Set(L.entries().map((e=>e[1].info.domain))),t={_configured:Array.from(L.entries()).filter((e=>Y(e[1]))).map((e=>q(L.get(e[0]))))};e.forEach((e=>{var n;t[e]=(n=e,Array.from(L.entries()).filter((e=>e[1].info.domain===n))).map((e=>q(L.get(e[0]))))})),D=t}),[L]),"stopped"==P||"unknown"==P?r().createElement("h3",null,"Enable plugin to see configuration"):r().createElement("div",null,r().createElement("div",{className:K.root},r().createElement(i.A,{variant:"contained",onClick:()=>{z("https://github.com/naugehyde/bt-sensors-plugin-sk/tree/1.2.0-beta#configuration")}},"Documentation"),r().createElement(i.A,{variant:"contained",onClick:()=>{z("https://github.com/naugehyde/bt-sensors-plugin-sk/issues/new/choose")}},"Report Issue"),r().createElement(i.A,{variant:"contained",onClick:()=>{z("https://discord.com/channels/1170433917761892493/1295425963466952725")}},"Discord Thread"),r().createElement("p",null),r().createElement("p",null)),r().createElement("hr",{style:{width:"100%",height:"1px",color:"gray","background-color":"gray","text-align":"left","margin-left":0}}),G?r().createElement("h2",{style:"color: red;"},G):"",r().createElement(a.Ay,{schema:n,validator:o.Ay,uiSchema:{"ui:field":"LayoutGridField","ui:layoutGrid":{"ui:row":[{"ui:row":{className:"row",children:[{"ui:columns":{className:"col-xs-4",children:["adapter","transport","duplicateData","discoveryTimeout","discoveryInterval"]}}]}}]}},onChange:e=>_(e.formData),onSubmit:({formData:e},t)=>{var n;n=e,console.log("updateBaseData"),F("updateBaseData",n).then((e=>{200!=e.status&&H(new Error(`Unable to update base data: ${e.statusText} (${e.status})`))})),$({})},onError:b("errors"),formData:C}),r().createElement("hr",{style:{width:"100%",height:"1px",color:"gray","background-color":"gray","text-align":"left","margin-left":0}}),r().createElement("p",null),r().createElement("p",null),B.deviceCount<B.totalDevices?r().createElement(S.A,{max:B.maxTimeout,now:B.progress}):"",r().createElement("p",null),r().createElement(w.A,{defaultActiveKey:"_configured",id:"domain-tabs",className:"mb-3",onClick:()=>{$({})}},Object.keys(D).map((e=>{return n=(t=e).slice("_"===t.charAt(0)?1:0),r().createElement(y.A,{eventKey:t,title:n.charAt(0).toUpperCase()+n.slice(1)},r().createElement(E.A,{style:{maxHeight:"300px",overflowY:"auto"}},function(e){return D[e]}(t)));var t,n}))),r().createElement("div",{style:{paddingLeft:10,paddingTop:10,display:0==Object.keys(x).length?"none":""}},r().createElement(c.A,{container:!0,direction:"column",style:{spacing:5}},r().createElement(c.A,{item:!0},r().createElement("h2",null,x?.title),r().createElement("p",null)),r().createElement(c.A,{item:!0},(0,l.Ay)(x?.htmlDescription))),r().createElement(a.Ay,{schema:x,validator:o.Ay,uiSchema:T,onChange:e=>{L.get(e.formData.mac_address)._changesMade=!0,U(e.formData)},onSubmit:({formData:e},t)=>{var n;n=e,console.log("updateSensorData"),F("updateSensorData",n).then((e=>{if(200!=e.status)throw new Error(e.statusText);L.get(n.mac_address)._changesMade=!1,L.get(n.mac_address).config=n})),$({}),alert("Changes saved")},onError:b("errors"),formData:O},r().createElement("div",{className:K.root},r().createElement(i.A,{type:"submit",color:"primary",variant:"contained"},"Save"),r().createElement(i.A,{variant:"contained",onClick:()=>{var e;e=O.mac_address,console.log("undoChanges"),L.get(e)._changesMade=!1,U(L.get(e).config)}},"Undo"),r().createElement(i.A,{variant:"contained",color:"secondary",onClick:e=>{return t=O.mac_address,void(window.confirm(`Delete configuration for ${t}?`)&&function(e){console.log("removeSensorData");try{F("removeSensorData",{mac_address:e}).then((e=>{if(200!=e.status)throw new Error(e.statusText)})),A.delete(e),J(new Map(A)),$({})}catch{}}(t));var t}},"Delete")))))}}}]);
|
package/public/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var e,r,t={49445:()=>{}},n={};function o(e){var r=n[e];if(void 0!==r)return r.exports;var a=n[e]={exports:{}};return t[e](a,a.exports,o),a.exports}o.m=t,o.c=n,o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((r,t)=>(o.f[t](e,r),r)),[])),o.u=e=>e+".js",o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="bt-sensors-plugin-sk:",o.l=(t,n,a,i)=>{if(e[t])e[t].push(n);else{var s,l;if(void 0!==a)for(var u=document.getElementsByTagName("script"),c=0;c<u.length;c++){var p=u[c];if(p.getAttribute("src")==t||p.getAttribute("data-webpack")==r+a){s=p;break}}s||(l=!0,(s=document.createElement("script")).charset="utf-8",s.timeout=120,o.nc&&s.setAttribute("nonce",o.nc),s.setAttribute("data-webpack",r+a),s.src=t),e[t]=[n];var d=(r,n)=>{s.onerror=s.onload=null,clearTimeout(f);var o=e[t];if(delete e[t],s.parentNode&&s.parentNode.removeChild(s),o&&o.forEach((e=>e(n))),r)return r(n)},f=setTimeout(d.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=d.bind(null,s.onerror),s.onload=d.bind(null,s.onload),l&&document.head.appendChild(s)}},(()=>{o.S={};var e={},r={};o.I=(t,n)=>{n||(n=[]);var a=r[t];if(a||(a=r[t]={}),!(n.indexOf(a)>=0)){if(n.push(a),e[t])return e[t];o.o(o.S,t)||(o.S[t]={});var i=o.S[t],s="bt-sensors-plugin-sk",l=[];return"default"===t&&((e,r,t,n)=>{var a=i[e]=i[e]||{},l=a[r];(!l||!l.loaded&&(1!=!l.eager?n:s>l.from))&&(a[r]={get:()=>o.e(
|
|
1
|
+
(()=>{var e,r,t={49445:()=>{}},n={};function o(e){var r=n[e];if(void 0!==r)return r.exports;var a=n[e]={exports:{}};return t[e](a,a.exports,o),a.exports}o.m=t,o.c=n,o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((r,t)=>(o.f[t](e,r),r)),[])),o.u=e=>e+".js",o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="bt-sensors-plugin-sk:",o.l=(t,n,a,i)=>{if(e[t])e[t].push(n);else{var s,l;if(void 0!==a)for(var u=document.getElementsByTagName("script"),c=0;c<u.length;c++){var p=u[c];if(p.getAttribute("src")==t||p.getAttribute("data-webpack")==r+a){s=p;break}}s||(l=!0,(s=document.createElement("script")).charset="utf-8",s.timeout=120,o.nc&&s.setAttribute("nonce",o.nc),s.setAttribute("data-webpack",r+a),s.src=t),e[t]=[n];var d=(r,n)=>{s.onerror=s.onload=null,clearTimeout(f);var o=e[t];if(delete e[t],s.parentNode&&s.parentNode.removeChild(s),o&&o.forEach((e=>e(n))),r)return r(n)},f=setTimeout(d.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=d.bind(null,s.onerror),s.onload=d.bind(null,s.onload),l&&document.head.appendChild(s)}},(()=>{o.S={};var e={},r={};o.I=(t,n)=>{n||(n=[]);var a=r[t];if(a||(a=r[t]={}),!(n.indexOf(a)>=0)){if(n.push(a),e[t])return e[t];o.o(o.S,t)||(o.S[t]={});var i=o.S[t],s="bt-sensors-plugin-sk",l=[];return"default"===t&&((e,r,t,n)=>{var a=i[e]=i[e]||{},l=a[r];(!l||!l.loaded&&(1!=!l.eager?n:s>l.from))&&(a[r]={get:()=>o.e(159).then((()=>()=>o(96540))),from:s,eager:!1})})("react","16.14.0"),e[t]=l.length?Promise.all(l).then((()=>e[t]=1)):1}}})(),(()=>{var e;o.g.importScripts&&(e=o.g.location+"");var r=o.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o.p=e})(),(()=>{var e={792:0};o.f.j=(r,t)=>{var n=o.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else{var a=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=a);var i=o.p+o.u(r),s=new Error;o.l(i,(t=>{if(o.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var a=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;s.message="Loading chunk "+r+" failed.\n("+a+": "+i+")",s.name="ChunkLoadError",s.type=a,s.request=i,n[1](s)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,a,[i,s,l]=t,u=0;if(i.some((r=>0!==e[r]))){for(n in s)o.o(s,n)&&(o.m[n]=s[n]);l&&l(o)}for(r&&r(t);u<i.length;u++)a=i[u],o.o(e,a)&&e[a]&&e[a][0](),e[a]=0},t=self.webpackChunkbt_sensors_plugin_sk=self.webpackChunkbt_sensors_plugin_sk||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),o(49445)})();
|
package/public/remoteEntry.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var bt_sensors_plugin_sk;(()=>{"use strict";var e,r,t,n,a,
|
|
1
|
+
var bt_sensors_plugin_sk;(()=>{"use strict";var e,r,t,n,o,a,i,u,s,l,f,d,c,p,h,v,g,b,m,y,w,k={43237:(e,r,t)=>{var n={"./PluginConfigurationPanel":()=>Promise.all([t.e(681),t.e(847)]).then((()=>()=>t(62995)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),a=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>a})}},S={};function _(e){var r=S[e];if(void 0!==r)return r.exports;var t=S[e]={id:e,loaded:!1,exports:{}};return k[e].call(t.exports,t,t.exports,_),t.loaded=!0,t.exports}_.m=k,_.c=S,_.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return _.d(r,{a:r}),r},_.d=(e,r)=>{for(var t in r)_.o(r,t)&&!_.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},_.f={},_.e=e=>Promise.all(Object.keys(_.f).reduce(((r,t)=>(_.f[t](e,r),r)),[])),_.u=e=>e+".js",_.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),_.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="bt-sensors-plugin-sk:",_.l=(t,n,o,a)=>{if(e[t])e[t].push(n);else{var i,u;if(void 0!==o)for(var s=document.getElementsByTagName("script"),l=0;l<s.length;l++){var f=s[l];if(f.getAttribute("src")==t||f.getAttribute("data-webpack")==r+o){i=f;break}}i||(u=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,_.nc&&i.setAttribute("nonce",_.nc),i.setAttribute("data-webpack",r+o),i.src=t),e[t]=[n];var d=(r,n)=>{i.onerror=i.onload=null,clearTimeout(c);var o=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach((e=>e(n))),r)return r(n)},c=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),u&&document.head.appendChild(i)}},_.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},_.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{_.S={};var e={},r={};_.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];_.o(_.S,t)||(_.S[t]={});var a=_.S[t],i="bt-sensors-plugin-sk",u=[];return"default"===t&&((e,r,t,n)=>{var o=a[e]=a[e]||{},u=o[r];(!u||!u.loaded&&(1!=!u.eager?n:i>u.from))&&(o[r]={get:()=>_.e(159).then((()=>()=>_(96540))),from:i,eager:!1})})("react","16.14.0"),e[t]=u.length?Promise.all(u).then((()=>e[t]=1)):1}}})(),(()=>{var e;_.g.importScripts&&(e=_.g.location+"");var r=_.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),_.p=e})(),t=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},n=(e,r)=>{e=t(e),r=t(r);for(var n=0;;){if(n>=e.length)return n<r.length&&"u"!=(typeof r[n])[0];var o=e[n],a=(typeof o)[0];if(n>=r.length)return"u"==a;var i=r[n],u=(typeof i)[0];if(a!=u)return"o"==a&&"n"==u||"s"==u||"u"==a;if("o"!=a&&"u"!=a&&o!=i)return o<i;n++}},o=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,a=1;a<e.length;a++)n--,t+="u"==(typeof(u=e[a]))[0]?"-":(n>0?".":"")+(n=2,u);return t}var i=[];for(a=1;a<e.length;a++){var u=e[a];i.push(0===u?"not("+s()+")":1===u?"("+s()+" || "+s()+")":2===u?i.pop()+" "+i.pop():o(u))}return s();function s(){return i.pop().replace(/^\((.+)\)$/,"$1")}},a=(e,r)=>{if(0 in e){r=t(r);var n=e[0],o=n<0;o&&(n=-n-1);for(var i=0,u=1,s=!0;;u++,i++){var l,f,d=u<e.length?(typeof e[u])[0]:"";if(i>=r.length||"o"==(f=(typeof(l=r[i]))[0]))return!s||("u"==d?u>n&&!o:""==d!=o);if("u"==f){if(!s||"u"!=d)return!1}else if(s)if(d==f)if(u<=n){if(l!=e[u])return!1}else{if(o?l>e[u]:l<e[u])return!1;l!=e[u]&&(s=!1)}else if("s"!=d&&"n"!=d){if(o||u<=n)return!1;s=!1,u--}else{if(u<=n||f<d!=o)return!1;s=!1}else"s"!=d&&"n"!=d&&(s=!1,u--)}}var c=[],p=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?p()|p():2==h?p()&p():h?a(h,r):!p())}return!!p()},i=(e,r)=>e&&_.o(e,r),u=e=>(e.loaded=1,e.get()),s=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),l=(e,r,t)=>{var o=t?s(e[r]):e[r];return(r=Object.keys(o).reduce(((e,r)=>!e||n(e,r)?r:e),0))&&o[r]},f=(e,r,t,o)=>{var i=o?s(e[r]):e[r];return(r=Object.keys(i).reduce(((e,r)=>!a(t,r)||e&&!n(e,r)?e:r),0))&&i[r]},d=(e,r,t,n,a)=>{var i=e[t];return"No satisfying version ("+o(n)+")"+(a?" for eager consumption":"")+" of shared module "+t+" found in shared scope "+r+".\nAvailable versions: "+Object.keys(i).map((e=>e+" from "+i[e].from)).join(", ")},c=e=>{throw new Error(e)},h=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(p=e=>function(r,t,n,o,a){var i=_.I(r);return i&&i.then&&!n?i.then(e.bind(e,r,_.S[r],t,!1,o,a)):e(r,_.S[r],t,n,o,a)})(((e,r,t,n,o)=>i(r,t)?u(l(r,t,n)):h(e,t,o))),g=p(((e,r,t,n,o,a)=>{if(!i(r,t))return h(e,t,a);var s=f(r,t,o,n);return s?u(s):a?a():void c(d(r,e,t,o,n))})),b={},m={21490:()=>g("default","react",!1,[,[1,17,0,0],[1,16,8,0],1],(()=>_.e(540).then((()=>()=>_(96540))))),42417:()=>g("default","react",!1,[0,16,8,0],(()=>_.e(540).then((()=>()=>_(96540))))),44167:()=>v("default","react",!1,(()=>_.e(540).then((()=>()=>_(96540))))),57920:()=>g("default","react",!1,[,[0,17],[1,16,14,0],1],(()=>_.e(540).then((()=>()=>_(96540))))),61104:()=>g("default","react",!1,[,[1,16,0,0,,0],[1,15,0,0],[2,0,14,0],1,1],(()=>_.e(540).then((()=>()=>_(96540))))),62085:()=>g("default","react",!1,[1,16,14,0],(()=>_.e(540).then((()=>()=>_(96540))))),86528:()=>g("default","react",!1,[1,16,13,1],(()=>_.e(540).then((()=>()=>_(96540))))),87227:()=>g("default","react",!1,[0,16,6,0],(()=>_.e(540).then((()=>()=>_(96540))))),96932:()=>g("default","react",!1,[0,15,0,0],(()=>_.e(540).then((()=>()=>_(96540))))),98271:()=>g("default","react",!1,[0,0,14,0],(()=>_.e(540).then((()=>()=>_(96540)))))},y={847:[21490,42417,44167,57920,61104,62085,86528,87227,96932,98271]},w={},_.f.consumes=(e,r)=>{_.o(y,e)&&y[e].forEach((e=>{if(_.o(b,e))return r.push(b[e]);if(!w[e]){var t=r=>{b[e]=0,_.m[e]=t=>{delete _.c[e],t.exports=r()}};w[e]=!0;var n=r=>{delete b[e],_.m[e]=t=>{throw delete _.c[e],r}};try{var o=m[e]();o.then?r.push(b[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={592:0};_.f.j=(r,t)=>{var n=_.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var a=_.p+_.u(r),i=new Error;_.l(a,(t=>{if(_.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+o+": "+a+")",i.name="ChunkLoadError",i.type=o,i.request=a,n[1](i)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[a,i,u]=t,s=0;if(a.some((r=>0!==e[r]))){for(n in i)_.o(i,n)&&(_.m[n]=i[n]);u&&u(_)}for(r&&r(t);s<a.length;s++)o=a[s],_.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=self.webpackChunkbt_sensors_plugin_sk=self.webpackChunkbt_sensors_plugin_sk||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})();var j=_(43237);bt_sensors_plugin_sk=j})();
|
package/sensor_classes/ATC.js
CHANGED
|
@@ -24,7 +24,7 @@ class ATC extends BTSensor{
|
|
|
24
24
|
{
|
|
25
25
|
title:'data parsing strategy',
|
|
26
26
|
type: 'string',
|
|
27
|
-
enum:["ATC-LE","ATC-BE"],
|
|
27
|
+
enum:["ATC-LE","ATC-BE", "ATC-Exploit"],
|
|
28
28
|
default: "ATC-LE"
|
|
29
29
|
}
|
|
30
30
|
)
|
|
@@ -51,6 +51,20 @@ class ATC extends BTSensor{
|
|
|
51
51
|
|
|
52
52
|
} else{
|
|
53
53
|
|
|
54
|
+
if (this.parser=="ATC-Exploit"){
|
|
55
|
+
this.getPath("batteryStrength")
|
|
56
|
+
.read=(buff)=>{return ((buff.readUInt8(9))/100)}
|
|
57
|
+
|
|
58
|
+
this.addDefaultPath('voltage','sensors.batteryVoltage')
|
|
59
|
+
.read=(buff)=>{return ((buff.readUInt16BE(10))/1000)}
|
|
60
|
+
|
|
61
|
+
this.addDefaultPath('temp', 'environment.temperature')
|
|
62
|
+
.read=(buff)=>{return parseFloat((273.15+(buff.readInt16BE(6))/10).toFixed(2))}
|
|
63
|
+
|
|
64
|
+
this.addDefaultPath('humidity','environment.humidity')
|
|
65
|
+
.read=(buff)=>{return ((buff.readUInt8(8))/100)}
|
|
66
|
+
}
|
|
67
|
+
else{
|
|
54
68
|
this.addDefaultPath('voltage','sensors.batteryVoltage')
|
|
55
69
|
.read=(buff)=>{return ((buff.readUInt16BE(10))/1000)}
|
|
56
70
|
|
|
@@ -59,6 +73,7 @@ class ATC extends BTSensor{
|
|
|
59
73
|
|
|
60
74
|
this.addDefaultPath('humidity','environment.humidity')
|
|
61
75
|
.read=(buff)=>{return ((buff.readUInt16BE(8))/10000)}
|
|
76
|
+
}
|
|
62
77
|
|
|
63
78
|
}
|
|
64
79
|
}
|
|
@@ -81,5 +96,11 @@ class ATC extends BTSensor{
|
|
|
81
96
|
getManufacturer(){
|
|
82
97
|
return "ATC1441 (custom firmware see: https://github.com/atc1441)"
|
|
83
98
|
}
|
|
99
|
+
|
|
100
|
+
getDescription(){
|
|
101
|
+
return `<div><p><img src="../bt-sensors-plugin-sk/images/LYWSD03MMC-Device.jpg" alt="ATC/LYWSD03MMC image" style="float: left; margin-right: 10px;" />
|
|
102
|
+
Xiaomi LYWSD03MMC custom firmware provided by <b>ATC1441</b>.
|
|
103
|
+
For more information, see: <a href=https://github.com/atc1441/ATC_MiThermometer?tab=readme-ov-file#atc_mithermometer target="_blank">ATC_MiThermometer</a><div>`
|
|
104
|
+
}
|
|
84
105
|
}
|
|
85
106
|
module.exports=ATC
|
|
@@ -3,10 +3,18 @@ class BLACKLISTED extends BTSensor {
|
|
|
3
3
|
static isSystem = true;
|
|
4
4
|
static async identify(device) {
|
|
5
5
|
const md = await this.getDeviceProp(device, "ManufacturerData");
|
|
6
|
+
const addrType = await this.getDeviceProp(device, "AddressType");
|
|
7
|
+
const addr = await this.getDeviceProp(device, "Address")
|
|
8
|
+
const addrFirstByte = addr?parseInt((addr)[0]):0
|
|
9
|
+
if (addrType=="random" && addrFirstByte >= 4 && addrFirstByte<=7) {
|
|
10
|
+
return this
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
if (md && Object.hasOwn(md, 0x004c)){
|
|
7
14
|
if (md[0x004c].value.slice(0,2).join() != [0x02, 0x15].join()){ // iBeacons are exempt
|
|
8
15
|
return this;
|
|
9
16
|
}
|
|
17
|
+
|
|
10
18
|
return null;
|
|
11
19
|
}
|
|
12
20
|
}
|
|
@@ -23,14 +31,9 @@ class BLACKLISTED extends BTSensor {
|
|
|
23
31
|
switch (this.getManufacturerID()) {
|
|
24
32
|
case 0x004c:
|
|
25
33
|
return "Randomized MAC address (Apple)";
|
|
26
|
-
|
|
27
|
-
return "Device is using VE.Smart"; //NOTE: Victron/VictronSensor class
|
|
28
|
-
//determines if a device is using VE.Smart
|
|
29
|
-
//in identify(). If so, identify() returns
|
|
30
|
-
//BlackListedDevice
|
|
31
|
-
|
|
34
|
+
|
|
32
35
|
default:
|
|
33
|
-
return "";
|
|
36
|
+
return "Random Private Address";
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -338,8 +338,23 @@ class GobiusCTankMeter extends BTSensor{
|
|
|
338
338
|
this.service = await this.gattServer.getPrimaryService("0000ffe0-0000-1000-8000-00805f9b34fb")
|
|
339
339
|
this.characteristic = await this.service.getCharacteristic("0000ffe9-0000-1000-8000-00805f9b34fb")
|
|
340
340
|
}
|
|
341
|
+
this.device.on("disconnect", ()=>{
|
|
342
|
+
if (this.isActive()) {
|
|
343
|
+
this.debug(`Device disconnected. Attempting to reconnect to ${this.getName()}`)
|
|
344
|
+
try {
|
|
345
|
+
this.deviceConnect().then(()=>{
|
|
346
|
+
this.debug(`Device reconnected -- ${this.getName()}`)
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
this.debug(`Error while reconnecting to ${this.getName()}`)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
|
|
341
355
|
resolve(this)
|
|
342
356
|
}).catch((e)=>{ reject(e.message) }) })
|
|
357
|
+
|
|
343
358
|
}
|
|
344
359
|
initGATTNotifications() {
|
|
345
360
|
Promise.resolve(this.characteristic.startNotifications().then(()=>{
|
|
@@ -348,7 +363,9 @@ class GobiusCTankMeter extends BTSensor{
|
|
|
348
363
|
})
|
|
349
364
|
}))
|
|
350
365
|
}
|
|
351
|
-
|
|
366
|
+
getDescription(){
|
|
367
|
+
return '<a href="https://gobiusc.com/"><img src="../bt-sensors-plugin-sk/images/Gobius_C.png" alt="Gobius C Tank Measure" "></a>'
|
|
368
|
+
}
|
|
352
369
|
async stopListening(){
|
|
353
370
|
super.stopListening()
|
|
354
371
|
if (this.characteristic && await this.characteristic.isNotifying()) {
|
package/sensor_classes/JBDBMS.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
const BTSensor = require("../BTSensor");
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
function sumByteArray(byteArray) {
|
|
3
|
+
let sum = 0;
|
|
4
|
+
for (let i = 0; i < byteArray.length; i++) {
|
|
5
|
+
sum += byteArray[i];
|
|
6
|
+
}
|
|
7
|
+
return sum;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function checkSum(buffer){
|
|
11
|
+
if (buffer.length<5) {
|
|
12
|
+
console.log (`Can't checksum ${buffer}. Invalid buffer. Buffer must be at least 5 bytes long.`)
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
const checksum = buffer.readUInt16BE(buffer.length-3)
|
|
16
|
+
let sum = sumByteArray(Uint8Array.prototype.slice.call(buffer, 2, buffer.length-3))
|
|
17
|
+
if ((0xffff-sum)+1 == checksum) return true
|
|
18
|
+
sum = sumByteArray(Uint8Array.prototype.slice.call(buffer, 2, buffer.length-2))
|
|
19
|
+
return (0xffff-sum)+1 == checksum
|
|
6
20
|
}
|
|
21
|
+
|
|
7
22
|
class JBDBMS extends BTSensor {
|
|
8
23
|
static Domain = BTSensor.SensorDomains.electrical
|
|
9
24
|
|
|
@@ -11,24 +26,18 @@ class JBDBMS extends BTSensor {
|
|
|
11
26
|
static NOTIFY_CHAR_UUID = "0000ff01-0000-1000-8000-00805f9b34fb"
|
|
12
27
|
static WRITE_CHAR_UUID = "0000ff02-0000-1000-8000-00805f9b34fb"
|
|
13
28
|
|
|
14
|
-
constructor(device,config,gattConfig){
|
|
15
|
-
super(device,config,gattConfig)
|
|
16
|
-
this.emitterFunctions=
|
|
17
|
-
[this.getAndEmitBatteryInfo.bind(this),
|
|
18
|
-
this.getAndEmitCellVoltages.bind(this)]
|
|
19
|
-
|
|
20
|
-
}
|
|
21
29
|
static identify(device){
|
|
22
30
|
return null
|
|
23
31
|
}
|
|
32
|
+
|
|
24
33
|
jbdCommand(command) {
|
|
25
34
|
return [0xDD, 0xA5, command, 0x00, 0xFF, 0xFF - (command - 1), 0x77]
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
async sendReadFunctionRequest( command ){
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
38
|
+
this.debug( `sending ${command}`)
|
|
39
|
+
return await this.txChar.writeValueWithoutResponse(Buffer.from(this.jbdCommand(command)))
|
|
40
|
+
}
|
|
32
41
|
|
|
33
42
|
async initSchema(){
|
|
34
43
|
super.initSchema()
|
|
@@ -62,11 +71,16 @@ class JBDBMS extends BTSensor {
|
|
|
62
71
|
(buffer)=>{return buffer.readUInt8(24)} )
|
|
63
72
|
.default="electrical.batteries.{batteryID}.FETControl"
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
await this.deviceConnect()
|
|
75
|
+
const gattServer = await this.device.gatt()
|
|
76
|
+
const txRxService= await gattServer.getPrimaryService(this.constructor.TX_RX_SERVICE)
|
|
77
|
+
this.rxChar = await txRxService.getCharacteristic(this.constructor.NOTIFY_CHAR_UUID)
|
|
78
|
+
this.txChar = await txRxService.getCharacteristic(this.constructor.WRITE_CHAR_UUID)
|
|
79
|
+
await this.rxChar.startNotifications()
|
|
80
|
+
|
|
81
|
+
const cellsAndTemps = await this.getNumberOfCellsAndTemps()
|
|
82
|
+
this.numberOfCells=cellsAndTemps.cells
|
|
83
|
+
this.numberOfTemps=cellsAndTemps.temps
|
|
70
84
|
|
|
71
85
|
for (let i=0; i<this.numberOfTemps; i++){
|
|
72
86
|
this.addMetadatum(`temp${i}`, 'K', `Temperature${i+1} reading`,
|
|
@@ -80,66 +94,75 @@ class JBDBMS extends BTSensor {
|
|
|
80
94
|
this.addMetadatum(`cell${i}Voltage`, 'V', `Cell ${i+1} voltage`,
|
|
81
95
|
(buffer)=>{return buffer.readUInt16BE((4+(i*2)))/1000} )
|
|
82
96
|
.default=`electrical.batteries.{batteryID}.cell${i}.voltage`
|
|
83
|
-
this.addMetadatum(`cell${i}Balance`, '
|
|
97
|
+
this.addMetadatum(`cell${i}Balance`, '', `Cell ${i+1} balance` )
|
|
84
98
|
.default=`electrical.batteries.{batteryID}.cell${i}.balance`
|
|
85
99
|
|
|
86
100
|
}
|
|
87
101
|
}
|
|
102
|
+
|
|
88
103
|
hasGATT(){
|
|
89
104
|
return true
|
|
90
105
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.rxChar = await txRxService.getCharacteristic(this.constructor.NOTIFY_CHAR_UUID)
|
|
95
|
-
this.txChar = await txRxService.getCharacteristic(this.constructor.WRITE_CHAR_UUID)
|
|
96
|
-
await this.rxChar.startNotifications()
|
|
97
|
-
return this
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async initGATTNotifications(){
|
|
101
|
-
this.intervalID = setInterval(()=>{
|
|
102
|
-
|
|
103
|
-
this.emitGATT()
|
|
106
|
+
initGATTNotifications(){
|
|
107
|
+
this.intervalID = setInterval( async ()=>{
|
|
108
|
+
await this.emitGATT()
|
|
104
109
|
}, 1000*(this?.pollFreq??60) )
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
emitGATT(){
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
async emitGATT(){
|
|
113
|
+
try {
|
|
114
|
+
await this.getAndEmitBatteryInfo()
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
this.debug(`Failed to emit battery info for ${this.getName()}: ${e}`)
|
|
118
|
+
}
|
|
119
|
+
setTimeout(async ()=>{
|
|
120
|
+
try {await this.getAndEmitCellVoltages()}
|
|
121
|
+
catch (e) {
|
|
122
|
+
this.debug(`Failed to emit Cell Voltages for ${this.getName()}: ${e}`)
|
|
123
|
+
}
|
|
124
|
+
}, 10000)
|
|
125
|
+
}
|
|
112
126
|
|
|
113
127
|
async getNumberOfCellsAndTemps(){
|
|
114
|
-
|
|
128
|
+
const b = await this.getBuffer(0x3)
|
|
115
129
|
return {cells:b[25], temps:b[26]}
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
|
|
119
|
-
getBuffer(command){
|
|
133
|
+
getBuffer (command){
|
|
120
134
|
|
|
121
135
|
return new Promise( async ( resolve, reject )=>{
|
|
122
136
|
const r = await this.sendReadFunctionRequest(command)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
let result = Buffer.alloc(256)
|
|
138
|
+
let offset = 0
|
|
139
|
+
let datasize = -1
|
|
126
140
|
const timer = setTimeout(() => {
|
|
127
141
|
clearTimeout(timer)
|
|
128
142
|
reject(new Error(`Response timed out from JBDBMS device ${this.getName()}. `));
|
|
129
143
|
}, 30000);
|
|
130
144
|
|
|
131
145
|
const valChanged = async (buffer) => {
|
|
146
|
+
if (offset==0){ //first packet
|
|
147
|
+
if (buffer[0]!==0xDD || buffer.length < 5 || buffer[1] !== command)
|
|
148
|
+
reject(`Invalid buffer from ${this.getName()}, not processing.`)
|
|
149
|
+
else
|
|
150
|
+
datasize=buffer[2]
|
|
151
|
+
}
|
|
132
152
|
buffer.copy(result,offset)
|
|
133
|
-
if (buffer[buffer.length-1]==0x77){
|
|
153
|
+
if (buffer[buffer.length-1]==0x77 && offset+buffer.length-6==datasize){
|
|
154
|
+
|
|
155
|
+
result = Uint8Array.prototype.slice.call(result, 0, offset+buffer.length)
|
|
134
156
|
this.debug(result)
|
|
135
157
|
this.rxChar.removeAllListeners()
|
|
136
158
|
clearTimeout(timer)
|
|
159
|
+
if (!checkSum(result))
|
|
160
|
+
reject(`Invalid checksum from ${this.getName()}, not processing.`)
|
|
161
|
+
|
|
137
162
|
resolve(result)
|
|
138
163
|
}
|
|
139
164
|
offset+=buffer.length
|
|
140
165
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
166
|
this.rxChar.on('valuechanged', valChanged )
|
|
144
167
|
})
|
|
145
168
|
}
|
|
@@ -148,8 +171,8 @@ async initGATTConnection() {
|
|
|
148
171
|
return this
|
|
149
172
|
}
|
|
150
173
|
|
|
151
|
-
getAndEmitBatteryInfo(){
|
|
152
|
-
this.getBuffer(0x03).then((buffer)=>{
|
|
174
|
+
async getAndEmitBatteryInfo(){
|
|
175
|
+
return this.getBuffer(0x03).then((buffer)=>{
|
|
153
176
|
(["current", "voltage", "remainingCapacity", "capacity","cycles", "protectionStatus", "SOC","FET",]).forEach((tag) =>
|
|
154
177
|
this.emitData( tag, buffer )
|
|
155
178
|
)
|
|
@@ -164,9 +187,10 @@ getAndEmitBatteryInfo(){
|
|
|
164
187
|
})
|
|
165
188
|
}
|
|
166
189
|
|
|
167
|
-
getAndEmitCellVoltages(){
|
|
168
|
-
this.getBuffer(0x4).then((buffer)=>{
|
|
169
|
-
|
|
190
|
+
async getAndEmitCellVoltages(){
|
|
191
|
+
return this.getBuffer(0x4).then((buffer)=>{
|
|
192
|
+
|
|
193
|
+
for (let i=0; i<this.numberOfCells; i++){
|
|
170
194
|
this.emitData(`cell${i}Voltage`,buffer)
|
|
171
195
|
}})
|
|
172
196
|
}
|
|
@@ -21,6 +21,10 @@ class ShellySBMO003Z extends AbstractBTHomeSensor {
|
|
|
21
21
|
*/
|
|
22
22
|
static LOCAL_NAME="Shelly BLU Motion";
|
|
23
23
|
|
|
24
|
+
getDescription(){
|
|
25
|
+
return `NOTE: Device must be paired with SignalK server machine to operate properly. For more information about the sensor go here: <a href=https://us.shelly.com/products/shelly-blu-motion target="_blank">Shelly Blu Motion</a>.`
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
initSchema() {
|
|
25
29
|
super.initSchema()
|
|
26
30
|
this.addDefaultParam("zone")
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class to support Batteries with embedded Shenzhen Li-ion Battery Bodyguard Technology BMS
|
|
3
|
+
* Brands include Redodo, Litime PowerQueen and possibly others
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const BTSensor = require("../BTSensor");
|
|
8
|
+
|
|
9
|
+
const ProtectionStatus = {
|
|
10
|
+
0x4:"Over Charge Protection",
|
|
11
|
+
0x20:"Over-discharge Protection",
|
|
12
|
+
0x40: "Charging Over Current Protection",
|
|
13
|
+
0x80: "Discharging Over Current Protection",
|
|
14
|
+
0x100: "High-temp Protection" ,
|
|
15
|
+
0x200: "High-temp Protection",
|
|
16
|
+
0x400: "Low-temp Protection",
|
|
17
|
+
0x800: "Low-temp Protection",
|
|
18
|
+
0x4000: "Short Circuit Protection"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class ShenzhenLiONBMS extends BTSensor{
|
|
22
|
+
static Domain = BTSensor.SensorDomains.electrical
|
|
23
|
+
static Commands = {
|
|
24
|
+
product_registration: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x01, 0x55, 0xAA, 0x05]),
|
|
25
|
+
disconnect_registration: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x02, 0x55, 0xAA, 0x06]),
|
|
26
|
+
query_battery_status: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x13, 0x55, 0xAA, 0x17]),
|
|
27
|
+
turn_on_charging: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x0a, 0x55, 0xAA, 0x0e]),
|
|
28
|
+
turn_off_charging: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x0b, 0x55, 0xAA, 0x0f]),
|
|
29
|
+
turn_on_discharge: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x0c, 0x55, 0xAA, 0x0e]),
|
|
30
|
+
turn_off_discharge: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x0d, 0x55, 0xAA, 0x0f]),
|
|
31
|
+
get_version: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x16, 0x55, 0xAA, 0x1a]),
|
|
32
|
+
get_serial_number: new Uint8Array([0x00, 0x00, 0x04, 0x01, 0x10, 0x55, 0xAA, 0x14])
|
|
33
|
+
};
|
|
34
|
+
static async identify(device){
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
hasGATT(){
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
usingGATT(){
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
emitGATT(){
|
|
44
|
+
this.characteristic.readValue()
|
|
45
|
+
.then((buffer)=>
|
|
46
|
+
this.emitValuesFrom( buffer)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
initSchema(){
|
|
51
|
+
super.initSchema()
|
|
52
|
+
this.getGATTParams()["useGATT"].default=true
|
|
53
|
+
|
|
54
|
+
this.addDefaultParam("batteryID").default="house"
|
|
55
|
+
|
|
56
|
+
this.addParameter(
|
|
57
|
+
"numberOfCells",
|
|
58
|
+
{
|
|
59
|
+
title:"Number of cells",
|
|
60
|
+
type: "integer",
|
|
61
|
+
default: 4,
|
|
62
|
+
minimum: 1,
|
|
63
|
+
maximum: 16
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
'protection_state': t.slice(76, 80).reverse().join(""),//Non all zeros means the BMS is "protecting" the battery somehow. (00000004 = Over Charge Protection, 00000020 = Over-discharge Protection, 00000040 = Charging Over Current Protection, 00000080 = Discharging Over Current Protection, 00000100 = High-temp Protection, 00000200 = High-temp Protection, 00000400 = Low-temp Protection, 00000800 = Low-temp Protection, 00004000 = Short Circuit Protection)
|
|
69
|
+
'heat': t.slice(68, 72).reverse().join(""),//discharge disabled due to app button = 00000080, heater_error = 00000002
|
|
70
|
+
'balance_memory_active': t.slice(72, 76).reverse().join(""),//activates when the battery has saved information about what cell it will balance
|
|
71
|
+
'failure_state': t.slice(80, 84).reverse().join("").slice(-3),//First numer/byte of the three regards cell_error, second number is also cell_error, third number is BMS_error
|
|
72
|
+
'is_balancing': HexTo2Str(t.slice(84, 88).reverse().join("")),//each bit represents a cell, if the bit = 1 that cell is balancing
|
|
73
|
+
'battery_state': t.slice(88, 90).reverse().join(""),//charge disabled = "0004", charging = "0001" (when charging active app will show estimated time untill fully charged), discharging/idle: "0000", unkown = "0002"
|
|
74
|
+
'discharges_count': HexTo10Str(t.slice(96, 100).reverse().join("")),
|
|
75
|
+
'discharges_amph_count': HexTo10Str(t.slice(100, 104).reverse().join("")),
|
|
76
|
+
*/
|
|
77
|
+
this.addMetadatum('measuredTotalVoltage','V', 'measured total voltage',
|
|
78
|
+
(buff)=>{return buff.readUInt32LE(8)/1000})
|
|
79
|
+
|
|
80
|
+
this.addDefaultPath('voltage', "electrical.batteries.voltage")
|
|
81
|
+
.read=(buff)=>{return buff.readUInt32LE(12)/1000}
|
|
82
|
+
|
|
83
|
+
for(let cellNum=0; cellNum < this?.numberOfCells??4; cellNum++) {
|
|
84
|
+
this.addMetadatum(`cell${cellNum+1}Voltage`,'V', `cell #${cellNum+1} voltage`,
|
|
85
|
+
(buff)=>{return buff.readInt16LE(16+(cellNum*2)) })
|
|
86
|
+
.default=`electrical.batteries.{batteryID}.cells.${cellNum+1}.voltage`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.addDefaultPath('current','electrical.batteries.current')
|
|
90
|
+
.read=(buff)=>{return buff.readInt32LE(48)/1000}
|
|
91
|
+
|
|
92
|
+
this.addMetadatum('cellTemp','K', 'cell temperature',
|
|
93
|
+
(buff)=>{return buff.readInt16LE(52) + 273.15})
|
|
94
|
+
.default="electrical.batteries.{batteryID}.cells.temperature"
|
|
95
|
+
|
|
96
|
+
this.addMetadatum('bmsTemp','K', 'bms temperature',
|
|
97
|
+
(buff)=>{return buff.readInt16LE(54) + 273.15})
|
|
98
|
+
.default="electrical.batteries.{batteryID}.bms.temperature"
|
|
99
|
+
|
|
100
|
+
this.addDefaultPath('remainingAh','electrical.batteries.capacity.remaining')
|
|
101
|
+
.read=(buff)=>{return this.buff.readUInt16LE(62)/100}
|
|
102
|
+
|
|
103
|
+
this.addDefaultPath('actualAh','electrical.batteries.capacity.actual')
|
|
104
|
+
.read=(buff)=>{return this.buff.readUInt16LE(64)/100}
|
|
105
|
+
|
|
106
|
+
this.addMetadatum('heat','', 'discharge disabled due to app button = 00000080, heater_error = 00000002',
|
|
107
|
+
(buff)=>{return buff.slice(68,72).reverse().join("")})
|
|
108
|
+
.default="electrical.batteries.{batteryID}.heat"
|
|
109
|
+
|
|
110
|
+
this.addMetadatum('balanceMemoryActive','', 'activates when the battery has saved information about what cell it will balance',
|
|
111
|
+
(buff)=>{return buff.slice(72,76).reverse().join("")})
|
|
112
|
+
.default="electrical.batteries.{batteryID}.balanceMemoryActive"
|
|
113
|
+
|
|
114
|
+
this.addMetadatum('protectionState','', 'protection state',
|
|
115
|
+
(buff)=>{return buff.slice(76,80).reverse().join("")})
|
|
116
|
+
.default="electrical.batteries.{batteryID}.protectionState"
|
|
117
|
+
|
|
118
|
+
this.addMetadatum('failureState','', 'failure state',
|
|
119
|
+
(buff)=>{return buff.slice(80,84).reverse().join("").slice(-3)})
|
|
120
|
+
.default="electrical.batteries.{batteryID}.failureState"
|
|
121
|
+
|
|
122
|
+
this.addMetadatum('balanceState','', '1 = cell at offset is balancing',
|
|
123
|
+
(buff)=>{return buff.slice(84,88).reverse().join("")})
|
|
124
|
+
.default="electrical.batteries.{batteryID}.balanceState"
|
|
125
|
+
|
|
126
|
+
this.addMetadatum('batteryState','', 'charge disabled = "0004", charging = "0001" (when charging active app will show estimated time untill fully charged), discharging/idle: "0000", unkown = "0002"',
|
|
127
|
+
(buff)=>{return buff.slice(88,90).reverse().join("")})
|
|
128
|
+
.default="electrical.batteries.{batteryID}.batteryState"
|
|
129
|
+
|
|
130
|
+
this.addMetadatum('dischargeCount','', 'discharge count',
|
|
131
|
+
(buff)=>{return buff.readUInt32LE(96)})
|
|
132
|
+
.default="electrical.batteries.{batteryID}.dischargeCount"
|
|
133
|
+
|
|
134
|
+
this.addMetadatum('dischargeAhCount','', 'discharge ah count',
|
|
135
|
+
(buff)=>{return buff.readUInt32LE(100)})
|
|
136
|
+
.default="electrical.batteries.{batteryID}.dischargeAhCount"
|
|
137
|
+
|
|
138
|
+
this.addDefaultPath( 'soc',"electrical.batteries.capacity.stateOfCharge")
|
|
139
|
+
.read=(buff)=>{return buff.readUInt16LE(90)/100}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
this.getJSONSchema().properties.params.required=["batteryID", "numberOfCells" ]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async initGATTConnection(){
|
|
146
|
+
|
|
147
|
+
return new Promise((resolve,reject )=>{
|
|
148
|
+
this.deviceConnect().then(async ()=>{
|
|
149
|
+
if (!this.gattServer) {
|
|
150
|
+
this.gattServer = await this.device.gatt()
|
|
151
|
+
this.service = await this.gattServer.getPrimaryService("0000ffe0-0000-1000-8000-00805f9b34fb")
|
|
152
|
+
this.rxCharacteristic = await this.service.getCharacteristic("0000ffe1-0000-1000-8000-00805f9b34fb")
|
|
153
|
+
this.txCharacteristic = await this.service.getCharacteristic("0000ffe2-0000-1000-8000-00805f9b34fb")
|
|
154
|
+
}
|
|
155
|
+
resolve(this)
|
|
156
|
+
})})
|
|
157
|
+
}
|
|
158
|
+
async initGATTNotifications() {
|
|
159
|
+
await this.txCharacteristic.writeValue( Buffer.from(this.constructor.Commands.query_battery_status))
|
|
160
|
+
await this.rxCharacteristic.startNotifications()
|
|
161
|
+
this.rxCharacteristic.on('valuechanged', buffer => {
|
|
162
|
+
this.emitValuesFrom(buffer)
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async stopListening(){
|
|
167
|
+
super.stopListening()
|
|
168
|
+
if (this.rxCharacteristic && await this.rxCharacteristic.isNotifying()) {
|
|
169
|
+
await this.rxCharacteristic.stopNotifications()
|
|
170
|
+
this.rxCharacteristic=null
|
|
171
|
+
}
|
|
172
|
+
if (await this.device.isConnected()){
|
|
173
|
+
await this.device.disconnect()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
module.exports=ShenzhenLiONBMS
|