@robdobsn/raftjs 1.1.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.
Files changed (149) hide show
  1. package/.editorconfig +14 -0
  2. package/.gitattributes +11 -0
  3. package/.nvmrc +1 -0
  4. package/LICENSE +22 -0
  5. package/README.md +11 -0
  6. package/TODO.md +1 -0
  7. package/dist/RaftAttributeHandler.d.ts +12 -0
  8. package/dist/RaftAttributeHandler.js +241 -0
  9. package/dist/RaftAttributeHandler.js.map +1 -0
  10. package/dist/RaftChannel.d.ts +18 -0
  11. package/dist/RaftChannel.js +12 -0
  12. package/dist/RaftChannel.js.map +1 -0
  13. package/dist/RaftChannelWebBLE.d.ts +38 -0
  14. package/dist/RaftChannelWebBLE.js +274 -0
  15. package/dist/RaftChannelWebBLE.js.map +1 -0
  16. package/dist/RaftChannelWebSerial.d.ts +37 -0
  17. package/dist/RaftChannelWebSerial.js +319 -0
  18. package/dist/RaftChannelWebSerial.js.map +1 -0
  19. package/dist/RaftChannelWebSocket.d.ts +28 -0
  20. package/dist/RaftChannelWebSocket.js +197 -0
  21. package/dist/RaftChannelWebSocket.js.map +1 -0
  22. package/dist/RaftCommsStats.d.ts +39 -0
  23. package/dist/RaftCommsStats.js +128 -0
  24. package/dist/RaftCommsStats.js.map +1 -0
  25. package/dist/RaftConnEvents.d.ts +31 -0
  26. package/dist/RaftConnEvents.js +44 -0
  27. package/dist/RaftConnEvents.js.map +1 -0
  28. package/dist/RaftConnector.d.ts +242 -0
  29. package/dist/RaftConnector.js +613 -0
  30. package/dist/RaftConnector.js.map +1 -0
  31. package/dist/RaftCustomAttrHandler.d.ts +4 -0
  32. package/dist/RaftCustomAttrHandler.js +50 -0
  33. package/dist/RaftCustomAttrHandler.js.map +1 -0
  34. package/dist/RaftDeviceInfo.d.ts +64 -0
  35. package/dist/RaftDeviceInfo.js +36 -0
  36. package/dist/RaftDeviceInfo.js.map +1 -0
  37. package/dist/RaftDeviceManager.d.ts +37 -0
  38. package/dist/RaftDeviceManager.js +450 -0
  39. package/dist/RaftDeviceManager.js.map +1 -0
  40. package/dist/RaftDeviceMsg.d.ts +9 -0
  41. package/dist/RaftDeviceMsg.js +11 -0
  42. package/dist/RaftDeviceMsg.js.map +1 -0
  43. package/dist/RaftDeviceStates.d.ts +33 -0
  44. package/dist/RaftDeviceStates.js +60 -0
  45. package/dist/RaftDeviceStates.js.map +1 -0
  46. package/dist/RaftFileHandler.d.ts +52 -0
  47. package/dist/RaftFileHandler.js +502 -0
  48. package/dist/RaftFileHandler.js.map +1 -0
  49. package/dist/RaftLog.d.ts +22 -0
  50. package/dist/RaftLog.js +63 -0
  51. package/dist/RaftLog.js.map +1 -0
  52. package/dist/RaftMiniHDLC.d.ts +18 -0
  53. package/dist/RaftMiniHDLC.js +383 -0
  54. package/dist/RaftMiniHDLC.js.map +1 -0
  55. package/dist/RaftMsgHandler.d.ts +57 -0
  56. package/dist/RaftMsgHandler.js +480 -0
  57. package/dist/RaftMsgHandler.js.map +1 -0
  58. package/dist/RaftMsgTrackInfo.d.ts +17 -0
  59. package/dist/RaftMsgTrackInfo.js +42 -0
  60. package/dist/RaftMsgTrackInfo.js.map +1 -0
  61. package/dist/RaftProtocolDefs.d.ts +30 -0
  62. package/dist/RaftProtocolDefs.js +48 -0
  63. package/dist/RaftProtocolDefs.js.map +1 -0
  64. package/dist/RaftStreamHandler.d.ts +38 -0
  65. package/dist/RaftStreamHandler.js +257 -0
  66. package/dist/RaftStreamHandler.js.map +1 -0
  67. package/dist/RaftSystemType.d.ts +21 -0
  68. package/dist/RaftSystemType.js +3 -0
  69. package/dist/RaftSystemType.js.map +1 -0
  70. package/dist/RaftSystemUtils.d.ts +136 -0
  71. package/dist/RaftSystemUtils.js +410 -0
  72. package/dist/RaftSystemUtils.js.map +1 -0
  73. package/dist/RaftTypes.d.ts +184 -0
  74. package/dist/RaftTypes.js +157 -0
  75. package/dist/RaftTypes.js.map +1 -0
  76. package/dist/RaftUpdateEvents.d.ts +33 -0
  77. package/dist/RaftUpdateEvents.js +46 -0
  78. package/dist/RaftUpdateEvents.js.map +1 -0
  79. package/dist/RaftUpdateManager.d.ts +61 -0
  80. package/dist/RaftUpdateManager.js +618 -0
  81. package/dist/RaftUpdateManager.js.map +1 -0
  82. package/dist/RaftUtils.d.ts +125 -0
  83. package/dist/RaftUtils.js +454 -0
  84. package/dist/RaftUtils.js.map +1 -0
  85. package/dist/RaftWifiTypes.d.ts +23 -0
  86. package/dist/RaftWifiTypes.js +43 -0
  87. package/dist/RaftWifiTypes.js.map +1 -0
  88. package/dist/TestDataGen.d.ts +7 -0
  89. package/dist/TestDataGen.js +133 -0
  90. package/dist/TestDataGen.js.map +1 -0
  91. package/dist/main.d.ts +18 -0
  92. package/dist/main.js +42 -0
  93. package/dist/main.js.map +1 -0
  94. package/eslint.config.mjs +33 -0
  95. package/examples/dashboard/package.json +39 -0
  96. package/examples/dashboard/src/ConnManager.ts +86 -0
  97. package/examples/dashboard/src/Main.tsx +100 -0
  98. package/examples/dashboard/src/StatusScreen.tsx +72 -0
  99. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +144 -0
  100. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +77 -0
  101. package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
  102. package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
  103. package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
  104. package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
  105. package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
  106. package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
  107. package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
  108. package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
  109. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +32 -0
  110. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
  111. package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
  112. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +113 -0
  113. package/examples/dashboard/src/index.html +15 -0
  114. package/examples/dashboard/src/index.tsx +15 -0
  115. package/examples/dashboard/src/styles.css +122 -0
  116. package/examples/dashboard/tsconfig.json +18 -0
  117. package/jest.config.js +11 -0
  118. package/package.json +50 -0
  119. package/src/RaftAttributeHandler.ts +289 -0
  120. package/src/RaftChannel.ts +30 -0
  121. package/src/RaftChannelWebBLE.ts +342 -0
  122. package/src/RaftChannelWebSerial.ts +408 -0
  123. package/src/RaftChannelWebSocket.ts +245 -0
  124. package/src/RaftCommsStats.ts +142 -0
  125. package/src/RaftConnEvents.ts +46 -0
  126. package/src/RaftConnector.ts +745 -0
  127. package/src/RaftCustomAttrHandler.ts +54 -0
  128. package/src/RaftDeviceInfo.ts +104 -0
  129. package/src/RaftDeviceManager.ts +542 -0
  130. package/src/RaftDeviceMsg.ts +20 -0
  131. package/src/RaftDeviceStates.ts +89 -0
  132. package/src/RaftFileHandler.ts +668 -0
  133. package/src/RaftLog.ts +70 -0
  134. package/src/RaftMiniHDLC.ts +396 -0
  135. package/src/RaftMsgHandler.ts +778 -0
  136. package/src/RaftMsgTrackInfo.ts +51 -0
  137. package/src/RaftProtocolDefs.ts +46 -0
  138. package/src/RaftStreamHandler.ts +328 -0
  139. package/src/RaftSystemType.ts +25 -0
  140. package/src/RaftSystemUtils.ts +487 -0
  141. package/src/RaftTypes.ts +250 -0
  142. package/src/RaftUpdateEvents.ts +48 -0
  143. package/src/RaftUpdateManager.ts +778 -0
  144. package/src/RaftUtils.ts +484 -0
  145. package/src/RaftWifiTypes.ts +36 -0
  146. package/src/TestDataGen.ts +157 -0
  147. package/src/main.ts +28 -0
  148. package/testdata/TestDeviceTypeRecs.json +492 -0
  149. package/tsconfig.json +27 -0
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3
+ //
4
+ // RaftCustomAttrHandler
5
+ // Custom attribute handler for Raft devices
6
+ //
7
+ // Rob Dobson (C) 2024
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ class CustomAttrHandler {
12
+ handleAttr(pollRespMetadata, msgBuffer, msgBufIdx) {
13
+ // Number of bytes in the each message
14
+ const numMsgBytes = pollRespMetadata.b;
15
+ // Create a vector for each attribute in the metadata
16
+ let attrValueVecs = [];
17
+ // Reference to each vector by attribute name
18
+ let attrValues = {};
19
+ // Add attributes to the vector
20
+ for (let attrIdx = 0; attrIdx < pollRespMetadata.a.length; attrIdx++) {
21
+ attrValueVecs.push([]);
22
+ attrValues[pollRespMetadata.a[attrIdx].n] = attrValueVecs[attrIdx];
23
+ }
24
+ // Custom code for each device type
25
+ if (pollRespMetadata.c.n === "max30101_fifo") {
26
+ // Hex dump msgBuffer
27
+ // console.log(`CustomAttrHandler handleAttr ${pollRespMetadata.c!.n} msgBuffer: ${msgBuffer.toString('hex')}`);
28
+ let buf = msgBuffer.slice(msgBufIdx);
29
+ if (buf.length < numMsgBytes) {
30
+ return [];
31
+ }
32
+ // Generated code ...
33
+ let N = (buf[0] + 32 - buf[2]) % 32;
34
+ let k = 3;
35
+ let i = 0;
36
+ while (i < N) {
37
+ attrValues['Red'].push(0);
38
+ attrValues['Red'][attrValues['Red'].length - 1] = (buf[k] << 16) | (buf[k + 1] << 8) | buf[k + 2];
39
+ attrValues['IR'].push(0);
40
+ attrValues['IR'][attrValues['IR'].length - 1] = (buf[k + 3] << 16) | (buf[k + 4] << 8) | buf[k + 5];
41
+ k += 6;
42
+ i++;
43
+ ;
44
+ }
45
+ }
46
+ return attrValueVecs;
47
+ }
48
+ }
49
+ exports.default = CustomAttrHandler;
50
+ //# sourceMappingURL=RaftCustomAttrHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RaftCustomAttrHandler.js","sourceRoot":"","sources":["../src/RaftCustomAttrHandler.ts"],"names":[],"mappings":";AAAA,iHAAiH;AACjH,EAAE;AACF,wBAAwB;AACxB,4CAA4C;AAC5C,EAAE;AACF,sBAAsB;AACtB,EAAE;AACF,iHAAiH;;AAIjH,MAAqB,iBAAiB;IAE3B,UAAU,CAAC,gBAA4C,EAAE,SAAiB,EAAE,SAAiB;QAEhG,sCAAsC;QACtC,MAAM,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC;QAEvC,qDAAqD;QACrD,IAAI,aAAa,GAAS,EAAE,CAAC;QAE7B,6CAA6C;QAC7C,IAAI,UAAU,GAAgC,EAAE,CAAC;QAEjD,+BAA+B;QAC/B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;YACnE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvB,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACvE,CAAC;QAED,mCAAmC;QACnC,IAAI,gBAAgB,CAAC,CAAE,CAAC,CAAC,KAAK,eAAe,EAAE,CAAC;YAC5C,qBAAqB;YACrB,iHAAiH;YACjH,IAAI,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACrC,IAAI,GAAG,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;gBAC3B,OAAO,EAAE,CAAC;YACd,CAAC;YAED,qBAAqB;YACrB,IAAI,CAAC,GAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAC,EAAE,GAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,GAAC,CAAC,CAAC;YACR,IAAI,CAAC,GAAC,CAAC,CAAC;YACR,OAAO,CAAC,GAAC,CAAC,EAAE,CAAC;gBACT,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAC,UAAU,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,GAAC,CAAC,CAAC,GAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAE,EAAE,CAAC,GAAC,CAAC,GAAG,CAAC,CAAC,GAAC,CAAC,CAAC,IAAE,CAAC,CAAC,GAAC,GAAG,CAAC,CAAC,GAAC,CAAC,CAAC,CAAC;gBAC9G,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAC,UAAU,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,GAAC,CAAC,CAAC,GAAE,CAAC,GAAG,CAAC,CAAC,GAAC,CAAC,CAAC,IAAE,EAAE,CAAC,GAAC,CAAC,GAAG,CAAC,CAAC,GAAC,CAAC,CAAC,IAAE,CAAC,CAAC,GAAC,GAAG,CAAC,CAAC,GAAC,CAAC,CAAC,CAAC;gBAC7G,CAAC,IAAE,CAAC,CAAC;gBACL,CAAC,EAAE,CAAC;gBACJ,CAAC;YACL,CAAC;QACL,CAAC;QACD,OAAO,aAAa,CAAC;IACzB,CAAC;CACJ;AA1CD,oCA0CC"}
@@ -0,0 +1,64 @@
1
+ export declare function getAttrTypeBits(attrType: string): number;
2
+ export declare function isAttrTypeSigned(attrType: string): boolean;
3
+ export declare function decodeAttrUnitsEncoding(unitsEncoding: string): string;
4
+ export interface DeviceTypeAttribute {
5
+ n: string;
6
+ t: string;
7
+ at?: number;
8
+ u?: string;
9
+ r?: number[];
10
+ x?: number;
11
+ m?: number | string;
12
+ s?: number;
13
+ sb?: number;
14
+ ss?: number;
15
+ d?: number;
16
+ a?: number;
17
+ f?: string;
18
+ o?: string;
19
+ v?: boolean | number;
20
+ vs?: boolean | number;
21
+ vf?: boolean | number;
22
+ }
23
+ export interface CustomFunctionDefinition {
24
+ n: string;
25
+ c: string;
26
+ }
27
+ export interface DeviceTypePollRespMetadata {
28
+ b: number;
29
+ a: DeviceTypeAttribute[];
30
+ c?: CustomFunctionDefinition;
31
+ us?: number;
32
+ }
33
+ export interface DeviceTypeAction {
34
+ n: string;
35
+ t?: string;
36
+ w: string;
37
+ r?: number[];
38
+ f?: string;
39
+ NX?: number;
40
+ NY?: number;
41
+ concat?: boolean;
42
+ d?: number;
43
+ }
44
+ export interface DeviceTypeInfo {
45
+ name: string;
46
+ desc: string;
47
+ manu: string;
48
+ type: string;
49
+ resp?: DeviceTypePollRespMetadata;
50
+ actions?: DeviceTypeAction[];
51
+ }
52
+ export interface DeviceTypeInfoRecs {
53
+ [devType: string]: DeviceTypeInfo;
54
+ }
55
+ export interface DeviceTypeInfoTestJsonRec {
56
+ addresses?: string;
57
+ devInfoJson: DeviceTypeInfo;
58
+ }
59
+ export interface DeviceTypeInfoTestJsonElem {
60
+ [devType: string]: DeviceTypeInfoTestJsonRec;
61
+ }
62
+ export interface DeviceTypeInfoTestJsonFile {
63
+ devTypes: DeviceTypeInfoTestJsonElem;
64
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3
+ //
4
+ // RaftDeviceInfo
5
+ // Device information for Raft devices
6
+ //
7
+ // Rob Dobson (C) 2024
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.getAttrTypeBits = getAttrTypeBits;
12
+ exports.isAttrTypeSigned = isAttrTypeSigned;
13
+ exports.decodeAttrUnitsEncoding = decodeAttrUnitsEncoding;
14
+ const attrTypeBits = {
15
+ "c": 8, "b": 8, "B": 8, "?": 8,
16
+ "h": 16, "H": 16, ">h": 16, "<h": 16, ">H": 16, "<H": 16,
17
+ "i": 32, "I": 32, ">i": 32, "<i": 32, ">I": 32, "<I": 32, "l": 32, "L": 32, ">l": 32, "<l": 32, ">L": 32, "<L": 32,
18
+ "q": 64, "Q": 64, ">q": 64, "<q": 64, ">Q": 64, "<Q": 64,
19
+ "f": 32, ">f": 32, "<f": 32,
20
+ "d": 64, ">d": 64, "<d": 64,
21
+ };
22
+ function getAttrTypeBits(attrType) {
23
+ if (attrType in attrTypeBits) {
24
+ return attrTypeBits[attrType];
25
+ }
26
+ return 8;
27
+ }
28
+ function isAttrTypeSigned(attrType) {
29
+ const attrStr = attrType.charAt(0) === ">" || attrType.charAt(0) === "<" ? attrType.slice(1).charAt(0) : attrType.charAt(0);
30
+ return attrStr === "b" || attrStr === "h" || attrStr === "i" || attrStr === "l" || attrStr === "q";
31
+ }
32
+ function decodeAttrUnitsEncoding(unitsEncoding) {
33
+ // Replace instances of HTML encoded chars like &deg; with the actual char
34
+ return unitsEncoding.replace(/&deg;/g, "°");
35
+ }
36
+ //# sourceMappingURL=RaftDeviceInfo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RaftDeviceInfo.js","sourceRoot":"","sources":["../src/RaftDeviceInfo.ts"],"names":[],"mappings":";AAAA,iHAAiH;AACjH,EAAE;AACF,iBAAiB;AACjB,sCAAsC;AACtC,EAAE;AACF,sBAAsB;AACtB,EAAE;AACF,iHAAiH;;AAWjH,0CAKC;AAED,4CAGC;AAED,0DAGC;AAxBD,MAAM,YAAY,GAA8B;IAC5C,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IAC9B,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IACxD,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAClH,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IACxD,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAC3B,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;CAC1B,CAAC;AAEN,SAAgB,eAAe,CAAC,QAAgB;IAC5C,IAAI,QAAQ,IAAI,YAAY,EAAE,CAAC;QAC3B,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,CAAC,CAAC;AACb,CAAC;AAED,SAAgB,gBAAgB,CAAC,QAAgB;IAC7C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5H,OAAO,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,GAAG,CAAC;AACvG,CAAC;AAED,SAAgB,uBAAuB,CAAC,aAAqB;IACzD,0EAA0E;IAC1E,OAAO,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { DeviceAttributeState, DevicesState, DeviceState } from "./RaftDeviceStates";
2
+ import { DeviceTypeAction } from "./RaftDeviceInfo";
3
+ import { DataType } from 'python-struct';
4
+ export declare class DeviceManager {
5
+ private static _instance;
6
+ private _maxDatapointsToStore;
7
+ private _attributeHandler;
8
+ private _serverAddressPrefix;
9
+ private _urlPrefix;
10
+ private _devicesState;
11
+ private _callbackNewDevice;
12
+ private _callbackNewDeviceAttribute;
13
+ private _callbackNewAttributeData;
14
+ private _lastStateUpdate;
15
+ private MAX_TIME_BETWEEN_STATE_UPDATES_MS;
16
+ private _websocket;
17
+ static getInstance(): DeviceManager;
18
+ getDevicesState(): DevicesState;
19
+ getDeviceState(deviceKey: string): DeviceState;
20
+ private _cachedDeviceTypeRecs;
21
+ private _testDeviceTypeRecs;
22
+ private _testDataGen;
23
+ private constructor();
24
+ sendCommand(cmd: string): Promise<boolean>;
25
+ init(): Promise<boolean>;
26
+ private connectWebSocket;
27
+ onNewDevice(callback: (deviceKey: string, state: DeviceState) => void): void;
28
+ onNewDeviceAttribute(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void;
29
+ onNewAttributeData(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void;
30
+ setFriendlyName(friendlyName: string): Promise<void>;
31
+ private handleClientMsgJson;
32
+ private processStateCallback;
33
+ private getDeviceTypeInfo;
34
+ sendAction(deviceKey: string, action: DeviceTypeAction, data: DataType[]): void;
35
+ sendCompoundAction(deviceKey: string, action: DeviceTypeAction, data: DataType[][]): void;
36
+ private hexToBytes;
37
+ }
@@ -0,0 +1,450 @@
1
+ "use strict";
2
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3
+ //
4
+ // RaftDeviceManager
5
+ // Device manager for Raft devices
6
+ //
7
+ // Rob Dobson (C) 2024
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.DeviceManager = void 0;
12
+ const tslib_1 = require("tslib");
13
+ const RaftDeviceStates_1 = require("./RaftDeviceStates");
14
+ const python_struct_1 = tslib_1.__importDefault(require("python-struct"));
15
+ const TestDataGen_1 = tslib_1.__importDefault(require("./TestDataGen"));
16
+ const RaftAttributeHandler_1 = tslib_1.__importDefault(require("./RaftAttributeHandler"));
17
+ let testingDeviceTypeRecsConditionalLoadPromise = null;
18
+ if (process.env.TEST_DATA) {
19
+ testingDeviceTypeRecsConditionalLoadPromise = Promise.resolve().then(() => tslib_1.__importStar(require('../testdata/TestDeviceTypeRecs.json')));
20
+ }
21
+ class DeviceManager {
22
+ // Get instance
23
+ static getInstance() {
24
+ if (!DeviceManager._instance) {
25
+ DeviceManager._instance = new DeviceManager();
26
+ }
27
+ return DeviceManager._instance;
28
+ }
29
+ getDevicesState() {
30
+ return this._devicesState;
31
+ }
32
+ getDeviceState(deviceKey) {
33
+ return this._devicesState[deviceKey];
34
+ }
35
+ // Constructor
36
+ constructor() {
37
+ // Max data points to store
38
+ this._maxDatapointsToStore = 1000;
39
+ // Attribute handler
40
+ this._attributeHandler = new RaftAttributeHandler_1.default();
41
+ // Server address
42
+ this._serverAddressPrefix = "";
43
+ // URL prefix
44
+ this._urlPrefix = "/api";
45
+ // Devices state
46
+ this._devicesState = new RaftDeviceStates_1.DevicesState();
47
+ // Device callbacks
48
+ this._callbackNewDevice = null;
49
+ this._callbackNewDeviceAttribute = null;
50
+ this._callbackNewAttributeData = null;
51
+ // Last time we got a state update
52
+ this._lastStateUpdate = 0;
53
+ this.MAX_TIME_BETWEEN_STATE_UPDATES_MS = 60000;
54
+ // Websocket
55
+ this._websocket = null;
56
+ // Cached device type data
57
+ this._cachedDeviceTypeRecs = {};
58
+ // Test device type data
59
+ this._testDeviceTypeRecs = null;
60
+ this._testDataGen = new TestDataGen_1.default();
61
+ // Check if test mode
62
+ // if (window.location.hostname === "localhost") {
63
+ if (process.env.TEST_DATA) {
64
+ this._testDataGen.start((msg) => {
65
+ this.handleClientMsgJson(msg);
66
+ });
67
+ }
68
+ }
69
+ ////////////////////////////////////////////////////////////////////////////
70
+ // Send REST commands
71
+ ////////////////////////////////////////////////////////////////////////////
72
+ async sendCommand(cmd) {
73
+ try {
74
+ const sendCommandResponse = await fetch(this._serverAddressPrefix + this._urlPrefix + cmd);
75
+ if (!sendCommandResponse.ok) {
76
+ console.warn(`DeviceManager sendCommand response not ok ${sendCommandResponse.status}`);
77
+ }
78
+ return sendCommandResponse.ok;
79
+ }
80
+ catch (error) {
81
+ console.warn(`DeviceManager sendCommand error ${error}`);
82
+ return false;
83
+ }
84
+ }
85
+ ////////////////////////////////////////////////////////////////////////////
86
+ // Init
87
+ ////////////////////////////////////////////////////////////////////////////
88
+ async init() {
89
+ // Check if already initialized
90
+ if (this._websocket) {
91
+ console.warn(`DeviceManager init already initialized`);
92
+ return true;
93
+ }
94
+ // console.log(`DeviceManager init - first time`)
95
+ // Conditionally load the device type records
96
+ if (testingDeviceTypeRecsConditionalLoadPromise) {
97
+ testingDeviceTypeRecsConditionalLoadPromise.then((jsonData) => {
98
+ this._testDeviceTypeRecs = jsonData;
99
+ });
100
+ }
101
+ // Websocket if not in test mode
102
+ if (!process.env.TEST_DATA) {
103
+ // Open websocket
104
+ const rslt = await this.connectWebSocket();
105
+ // Start timer to check for websocket reconnection
106
+ setInterval(async () => {
107
+ var _a;
108
+ if (!this._websocket) {
109
+ console.log(`DeviceManager init - reconnecting websocket`);
110
+ await this.connectWebSocket();
111
+ }
112
+ else if ((Date.now() - this._lastStateUpdate) > this.MAX_TIME_BETWEEN_STATE_UPDATES_MS) {
113
+ const inactiveTimeSecs = ((Date.now() - this._lastStateUpdate) / 1000).toFixed(1);
114
+ if (this._websocket) {
115
+ console.log(`DeviceManager init - closing websocket due to ${inactiveTimeSecs}s inactivity`);
116
+ this._websocket.close();
117
+ this._websocket = null;
118
+ }
119
+ }
120
+ console.log(`websocket state ${(_a = this._websocket) === null || _a === void 0 ? void 0 : _a.readyState}`);
121
+ }, 5000);
122
+ return rslt;
123
+ }
124
+ // Test mode
125
+ return true;
126
+ }
127
+ ////////////////////////////////////////////////////////////////////////////
128
+ // Open websocket
129
+ ////////////////////////////////////////////////////////////////////////////
130
+ async connectWebSocket() {
131
+ // Open a websocket to the server
132
+ try {
133
+ console.log(`DeviceManager init location.origin ${window.location.origin} ${window.location.protocol} ${window.location.host} ${window.location.hostname} ${window.location.port} ${window.location.pathname} ${window.location.search} ${window.location.hash}`);
134
+ let webSocketURL = this._serverAddressPrefix;
135
+ if (webSocketURL.startsWith("http")) {
136
+ webSocketURL = webSocketURL.replace(/^http/, 'ws');
137
+ }
138
+ else {
139
+ webSocketURL = window.location.origin.replace(/^http/, 'ws');
140
+ }
141
+ webSocketURL += "/devjson";
142
+ console.log(`DeviceManager init opening websocket ${webSocketURL}`);
143
+ this._websocket = new WebSocket(webSocketURL);
144
+ if (!this._websocket) {
145
+ console.error("DeviceManager init unable to create websocket");
146
+ return false;
147
+ }
148
+ this._websocket.binaryType = "arraybuffer";
149
+ this._lastStateUpdate = Date.now();
150
+ this._websocket.onopen = () => {
151
+ // Debug
152
+ console.log(`DeviceManager init websocket opened to ${webSocketURL}`);
153
+ // Send subscription request messages after a short delay
154
+ setTimeout(() => {
155
+ // Subscribe to device messages
156
+ const subscribeName = "devices";
157
+ console.log(`DeviceManager init subscribing to ${subscribeName}`);
158
+ if (this._websocket) {
159
+ this._websocket.send(JSON.stringify({
160
+ cmdName: "subscription",
161
+ action: "update",
162
+ pubRecs: [
163
+ { name: subscribeName, msgID: subscribeName, rateHz: 0.1 },
164
+ ]
165
+ }));
166
+ }
167
+ }, 1000);
168
+ };
169
+ this._websocket.onmessage = (event) => {
170
+ this.handleClientMsgJson(event.data);
171
+ };
172
+ this._websocket.onclose = () => {
173
+ console.log(`DeviceManager websocket closed`);
174
+ this._websocket = null;
175
+ };
176
+ this._websocket.onerror = (error) => {
177
+ console.warn(`DeviceManager websocket error ${error}`);
178
+ };
179
+ }
180
+ catch (error) {
181
+ console.warn(`DeviceManager websocket error ${error}`);
182
+ return false;
183
+ }
184
+ return true;
185
+ }
186
+ ////////////////////////////////////////////////////////////////////////////
187
+ // Callbacks
188
+ ////////////////////////////////////////////////////////////////////////////
189
+ // Register state change callbacks
190
+ onNewDevice(callback) {
191
+ // Save the callback
192
+ this._callbackNewDevice = callback;
193
+ }
194
+ onNewDeviceAttribute(callback) {
195
+ // Save the callback
196
+ this._callbackNewDeviceAttribute = callback;
197
+ }
198
+ onNewAttributeData(callback) {
199
+ // Save the callback
200
+ this._callbackNewAttributeData = callback;
201
+ }
202
+ ////////////////////////////////////////////////////////////////////////////
203
+ // Set the friendly name for the device
204
+ ////////////////////////////////////////////////////////////////////////////
205
+ async setFriendlyName(friendlyName) {
206
+ try {
207
+ await fetch(this._serverAddressPrefix + this._urlPrefix + "/friendlyname/" + friendlyName);
208
+ }
209
+ catch (error) {
210
+ console.log(`DeviceManager setFriendlyName ${error}`);
211
+ }
212
+ }
213
+ ////////////////////////////////////////////////////////////////////////////
214
+ // Handle device message JSON
215
+ ////////////////////////////////////////////////////////////////////////////
216
+ handleClientMsgJson(jsonMsg) {
217
+ const removeDevicesNoLongerPresent = true;
218
+ let data = JSON.parse(jsonMsg);
219
+ // console.log(`DeviceManager websocket message ${JSON.stringify(data)}`);
220
+ // Iterate over the buses
221
+ Object.entries(data).forEach(([busName, devices]) => {
222
+ // Check for bus status info
223
+ if (devices && typeof devices === "object" && "_s" in devices) {
224
+ // console.log(`DeviceManager bus status ${JSON.stringify(devices._s)}`);
225
+ return;
226
+ }
227
+ // Get a list of keys for the current devicesState
228
+ const deviceKeysToRemove = Object.keys(this._devicesState);
229
+ // Iterate over the devices
230
+ Object.entries(devices).forEach(async ([devAddr, attrGroups]) => {
231
+ // Check for non-device info (starts with _)
232
+ if (devAddr.startsWith("_")) {
233
+ return;
234
+ }
235
+ // Device key
236
+ const deviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busName, devAddr);
237
+ // Remove from the list of keys for the current devicesState
238
+ const idx = deviceKeysToRemove.indexOf(deviceKey);
239
+ if (idx >= 0) {
240
+ deviceKeysToRemove.splice(idx, 1);
241
+ }
242
+ // Check if a device state already exists
243
+ if (!(deviceKey in this._devicesState)) {
244
+ let deviceTypeName = "";
245
+ if (attrGroups && typeof attrGroups === 'object' && "_t" in attrGroups && typeof attrGroups._t === "string") {
246
+ deviceTypeName = attrGroups._t || "";
247
+ }
248
+ else {
249
+ console.warn(`DeviceManager missing device type attrGroups ${JSON.stringify(attrGroups)}`);
250
+ return;
251
+ }
252
+ // Create device record
253
+ this._devicesState[deviceKey] = {
254
+ deviceTypeInfo: await this.getDeviceTypeInfo(busName, devAddr, deviceTypeName),
255
+ deviceTimeline: {
256
+ timestampsUs: [],
257
+ lastReportTimestampUs: 0,
258
+ reportTimestampOffsetUs: 0
259
+ },
260
+ deviceAttributes: {},
261
+ deviceIsNew: true,
262
+ stateChanged: false,
263
+ isOnline: true
264
+ };
265
+ }
266
+ // Get device state
267
+ const deviceState = this._devicesState[deviceKey];
268
+ // Check for online/offline state information
269
+ if (attrGroups && typeof attrGroups === "object" && "_o" in attrGroups) {
270
+ deviceState.isOnline = ((attrGroups._o === true) || (attrGroups._o === "1") || (attrGroups._o === 1));
271
+ }
272
+ // Iterate attribute groups
273
+ Object.entries(attrGroups).forEach(([attrGroupName, msgHexStr]) => {
274
+ // Check valid
275
+ if (attrGroupName.startsWith("_") || (typeof msgHexStr != 'string')) {
276
+ return;
277
+ }
278
+ // Check the device type info
279
+ if (!deviceState.deviceTypeInfo.resp) {
280
+ return;
281
+ }
282
+ // Convert the hex string to an arraybuffer by converting each pair of hex chars to a byte
283
+ const msgBytes = this.hexToBytes(msgHexStr);
284
+ // Convert to a Buffer
285
+ const msgBuffer = Buffer.from(msgBytes);
286
+ // Work through the message which may contain multiple data instances
287
+ let msgBufIdx = 0;
288
+ // Iterate over attributes in the group
289
+ const pollRespMetadata = deviceState.deviceTypeInfo.resp;
290
+ // Loop
291
+ while (msgBufIdx < msgBytes.length) {
292
+ const curTimelineLen = deviceState.deviceTimeline.timestampsUs.length;
293
+ const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(msgBuffer, msgBufIdx, deviceState.deviceTimeline, pollRespMetadata, deviceState.deviceAttributes, this._maxDatapointsToStore);
294
+ if (newMsgBufIdx < 0)
295
+ break;
296
+ msgBufIdx = newMsgBufIdx;
297
+ if (deviceState.deviceTimeline.timestampsUs.length !== curTimelineLen) {
298
+ deviceState.stateChanged = true;
299
+ }
300
+ }
301
+ });
302
+ });
303
+ // Remove devices no longer present
304
+ if (removeDevicesNoLongerPresent) {
305
+ deviceKeysToRemove.forEach((deviceKey) => {
306
+ delete this._devicesState[deviceKey];
307
+ });
308
+ }
309
+ });
310
+ // Update the last state update time
311
+ this._lastStateUpdate = Date.now();
312
+ // Process the callback
313
+ this.processStateCallback();
314
+ }
315
+ ////////////////////////////////////////////////////////////////////////////
316
+ // Process state change callback
317
+ ////////////////////////////////////////////////////////////////////////////
318
+ processStateCallback() {
319
+ // Iterate over the devices
320
+ Object.entries(this._devicesState).forEach(([deviceKey, deviceState]) => {
321
+ // Check if device record is new
322
+ if (deviceState.deviceIsNew) {
323
+ if (this._callbackNewDevice) {
324
+ this._callbackNewDevice(deviceKey, deviceState);
325
+ }
326
+ deviceState.deviceIsNew = false;
327
+ }
328
+ // Iterate over the attributes
329
+ Object.entries(deviceState.deviceAttributes).forEach(([_attrKey, attrState]) => {
330
+ if (attrState.newAttribute) {
331
+ if (this._callbackNewDeviceAttribute) {
332
+ this._callbackNewDeviceAttribute(deviceKey, attrState);
333
+ }
334
+ attrState.newAttribute = false;
335
+ }
336
+ if (attrState.newData) {
337
+ if (this._callbackNewAttributeData) {
338
+ this._callbackNewAttributeData(deviceKey, attrState);
339
+ }
340
+ attrState.newData = false;
341
+ }
342
+ });
343
+ });
344
+ }
345
+ ////////////////////////////////////////////////////////////////////////////
346
+ // Get device type info
347
+ ////////////////////////////////////////////////////////////////////////////
348
+ async getDeviceTypeInfo(busName, _devAddr, deviceType) {
349
+ const emptyRec = {
350
+ "name": "Unknown",
351
+ "desc": "Unknown",
352
+ "manu": "Unknown",
353
+ "type": "Unknown"
354
+ };
355
+ // Ensure that this._testDeviceTypeRecs and devTypes[deviceType] are properly initialized
356
+ if (process.env.TEST_DATA) {
357
+ if (this._testDeviceTypeRecs && this._testDeviceTypeRecs.devTypes[deviceType]) {
358
+ return this._testDeviceTypeRecs.devTypes[deviceType].devInfoJson;
359
+ }
360
+ else {
361
+ // Handle the case where the necessary data isn't available
362
+ console.error("Device type info not available for:", deviceType);
363
+ return emptyRec;
364
+ }
365
+ }
366
+ // Check if already in the cache
367
+ if (deviceType in this._cachedDeviceTypeRecs) {
368
+ return this._cachedDeviceTypeRecs[deviceType];
369
+ }
370
+ // Get the device type info from the server
371
+ try {
372
+ const getDevTypeInfoResponse = await fetch(this._serverAddressPrefix + this._urlPrefix + "/devman/typeinfo?bus=" + busName + "&type=" + deviceType);
373
+ if (!getDevTypeInfoResponse.ok) {
374
+ console.error(`DeviceManager getDeviceTypeInfo response not ok ${getDevTypeInfoResponse.status}`);
375
+ return emptyRec;
376
+ }
377
+ const devTypeInfo = await getDevTypeInfoResponse.json();
378
+ if ("devinfo" in devTypeInfo) {
379
+ this._cachedDeviceTypeRecs[deviceType] = devTypeInfo.devinfo;
380
+ return devTypeInfo.devinfo;
381
+ }
382
+ return emptyRec;
383
+ }
384
+ catch (error) {
385
+ console.error(`DeviceManager getDeviceTypeInfo error ${error}`);
386
+ return emptyRec;
387
+ }
388
+ }
389
+ ////////////////////////////////////////////////////////////////////////////
390
+ // Send action to device
391
+ ////////////////////////////////////////////////////////////////////////////
392
+ sendAction(deviceKey, action, data) {
393
+ // console.log(`DeviceManager sendAction ${deviceKey} action name ${action.n} value ${value} prefix ${action.w}`);
394
+ // Form the write bytes
395
+ let writeBytes = action.t ? python_struct_1.default.pack(action.t, data) : Buffer.from([]);
396
+ // Convert to hex string
397
+ let writeHexStr = Buffer.from(writeBytes).toString('hex');
398
+ // Add prefix
399
+ writeHexStr = action.w + writeHexStr;
400
+ // Separate the bus and address in the deviceKey (_ char)
401
+ const devBus = deviceKey.split("_")[0];
402
+ const devAddr = deviceKey.split("_")[1];
403
+ // Send the action to the server
404
+ const url = this._serverAddressPrefix + this._urlPrefix + "/devman/cmdraw?bus=" + devBus + "&addr=" + devAddr + "&hexWr=" + writeHexStr;
405
+ console.log(`DeviceManager deviceKey ${deviceKey} action name ${action.n} value ${data} prefix ${action.w} sendAction ${url}`);
406
+ fetch(url)
407
+ .then(response => {
408
+ if (!response.ok) {
409
+ console.error(`DeviceManager sendAction response not ok ${response.status}`);
410
+ }
411
+ })
412
+ .catch(error => {
413
+ console.error(`DeviceManager sendAction error ${error}`);
414
+ });
415
+ }
416
+ ////////////////////////////////////////////////////////////////////////////
417
+ // Send a compound action to the device
418
+ ////////////////////////////////////////////////////////////////////////////
419
+ sendCompoundAction(deviceKey, action, data) {
420
+ // console.log(`DeviceManager sendAction ${deviceKey} action name ${action.n} value ${value} prefix ${action.w}`);
421
+ // Check if all data to be sent at once
422
+ if (action.concat) {
423
+ // Form a single list by flattening data
424
+ let dataToWrite = [];
425
+ for (let dataIdx = 0; dataIdx < data.length; dataIdx++) {
426
+ dataToWrite = dataToWrite.concat(data[dataIdx]);
427
+ }
428
+ // Use sendAction to send this
429
+ this.sendAction(deviceKey, action, dataToWrite);
430
+ }
431
+ else {
432
+ // Iterate over the data
433
+ for (let dataIdx = 0; dataIdx < data.length; dataIdx++) {
434
+ // Create the data to write by prepending the index to the data for this index
435
+ let dataToWrite = [dataIdx].concat(data[dataIdx]);
436
+ // Use sendAction to send this
437
+ this.sendAction(deviceKey, action, dataToWrite);
438
+ }
439
+ }
440
+ }
441
+ hexToBytes(hex) {
442
+ const bytes = new Uint8Array(hex.length / 2);
443
+ for (let i = 0; i < bytes.length; i++) {
444
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
445
+ }
446
+ return bytes;
447
+ }
448
+ }
449
+ exports.DeviceManager = DeviceManager;
450
+ //# sourceMappingURL=RaftDeviceManager.js.map