@robotical/raftjs 1.4.7 → 2.0.3

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 (133) hide show
  1. package/dist/react-native/RaftAttributeHandler.d.ts +2 -0
  2. package/dist/react-native/RaftAttributeHandler.js +136 -10
  3. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  4. package/dist/react-native/RaftChannel.d.ts +2 -0
  5. package/dist/react-native/RaftChannelBLE.native.d.ts +2 -0
  6. package/dist/react-native/RaftChannelBLE.native.js +14 -7
  7. package/dist/react-native/RaftChannelBLE.native.js.map +1 -1
  8. package/dist/react-native/RaftChannelBLE.web.d.ts +2 -0
  9. package/dist/react-native/RaftChannelBLE.web.js +24 -15
  10. package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
  11. package/dist/react-native/RaftChannelSimulated.d.ts +32 -0
  12. package/dist/react-native/RaftChannelSimulated.js +418 -0
  13. package/dist/react-native/RaftChannelSimulated.js.map +1 -0
  14. package/dist/react-native/RaftChannelWebSerial.d.ts +2 -0
  15. package/dist/react-native/RaftChannelWebSerial.js +18 -9
  16. package/dist/react-native/RaftChannelWebSerial.js.map +1 -1
  17. package/dist/react-native/RaftChannelWebSocket.d.ts +2 -0
  18. package/dist/react-native/RaftChannelWebSocket.js +10 -0
  19. package/dist/react-native/RaftChannelWebSocket.js.map +1 -1
  20. package/dist/react-native/RaftConnector.js +13 -6
  21. package/dist/react-native/RaftConnector.js.map +1 -1
  22. package/dist/react-native/RaftCustomAttrHandler.js +15 -0
  23. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
  24. package/dist/react-native/RaftDeviceInfo.d.ts +8 -1
  25. package/dist/react-native/RaftDeviceManager.d.ts +3 -0
  26. package/dist/react-native/RaftDeviceManager.js +123 -43
  27. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  28. package/dist/react-native/RaftFileHandler.js +8 -8
  29. package/dist/react-native/RaftFileHandler.js.map +1 -1
  30. package/dist/react-native/RaftLog.js +1 -1
  31. package/dist/react-native/RaftLog.js.map +1 -1
  32. package/dist/react-native/RaftMsgHandler.d.ts +5 -0
  33. package/dist/react-native/RaftMsgHandler.js +32 -0
  34. package/dist/react-native/RaftMsgHandler.js.map +1 -1
  35. package/dist/react-native/RaftStreamHandler.js +6 -5
  36. package/dist/react-native/RaftStreamHandler.js.map +1 -1
  37. package/dist/react-native/RaftStruct.js +1 -1
  38. package/dist/react-native/RaftStruct.js.map +1 -1
  39. package/dist/react-native/RaftSysTypeManager.d.ts +2 -0
  40. package/dist/react-native/RaftSysTypeManager.js +25 -0
  41. package/dist/react-native/RaftSysTypeManager.js.map +1 -1
  42. package/dist/react-native/RaftSystemType.d.ts +9 -7
  43. package/dist/react-native/RaftSystemUtils.js +3 -1
  44. package/dist/react-native/RaftSystemUtils.js.map +1 -1
  45. package/dist/react-native/RaftUpdateManager.js +2 -2
  46. package/dist/react-native/RaftUpdateManager.js.map +1 -1
  47. package/dist/react-native/RaftUtils.d.ts +2 -0
  48. package/dist/react-native/RaftUtils.js +22 -2
  49. package/dist/react-native/RaftUtils.js.map +1 -1
  50. package/dist/react-native/main.d.ts +2 -0
  51. package/dist/react-native/main.js +4 -1
  52. package/dist/react-native/main.js.map +1 -1
  53. package/dist/web/RaftAttributeHandler.d.ts +2 -0
  54. package/dist/web/RaftAttributeHandler.js +136 -10
  55. package/dist/web/RaftAttributeHandler.js.map +1 -1
  56. package/dist/web/RaftChannel.d.ts +2 -0
  57. package/dist/web/RaftChannelBLE.web.d.ts +2 -0
  58. package/dist/web/RaftChannelBLE.web.js +24 -15
  59. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  60. package/dist/web/RaftChannelSimulated.d.ts +32 -0
  61. package/dist/web/RaftChannelSimulated.js +418 -0
  62. package/dist/web/RaftChannelSimulated.js.map +1 -0
  63. package/dist/web/RaftChannelWebSerial.d.ts +2 -0
  64. package/dist/web/RaftChannelWebSerial.js +18 -9
  65. package/dist/web/RaftChannelWebSerial.js.map +1 -1
  66. package/dist/web/RaftChannelWebSocket.d.ts +2 -0
  67. package/dist/web/RaftChannelWebSocket.js +10 -0
  68. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  69. package/dist/web/RaftConnector.js +13 -6
  70. package/dist/web/RaftConnector.js.map +1 -1
  71. package/dist/web/RaftCustomAttrHandler.js +15 -0
  72. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  73. package/dist/web/RaftDeviceInfo.d.ts +8 -1
  74. package/dist/web/RaftDeviceManager.d.ts +3 -0
  75. package/dist/web/RaftDeviceManager.js +123 -43
  76. package/dist/web/RaftDeviceManager.js.map +1 -1
  77. package/dist/web/RaftFileHandler.js +8 -8
  78. package/dist/web/RaftFileHandler.js.map +1 -1
  79. package/dist/web/RaftLog.js +1 -1
  80. package/dist/web/RaftLog.js.map +1 -1
  81. package/dist/web/RaftMsgHandler.d.ts +5 -0
  82. package/dist/web/RaftMsgHandler.js +32 -0
  83. package/dist/web/RaftMsgHandler.js.map +1 -1
  84. package/dist/web/RaftStreamHandler.js +6 -5
  85. package/dist/web/RaftStreamHandler.js.map +1 -1
  86. package/dist/web/RaftStruct.js +1 -1
  87. package/dist/web/RaftStruct.js.map +1 -1
  88. package/dist/web/RaftSysTypeManager.d.ts +2 -0
  89. package/dist/web/RaftSysTypeManager.js +25 -0
  90. package/dist/web/RaftSysTypeManager.js.map +1 -1
  91. package/dist/web/RaftSystemType.d.ts +9 -7
  92. package/dist/web/RaftSystemUtils.js +3 -1
  93. package/dist/web/RaftSystemUtils.js.map +1 -1
  94. package/dist/web/RaftUpdateManager.js +2 -2
  95. package/dist/web/RaftUpdateManager.js.map +1 -1
  96. package/dist/web/RaftUtils.d.ts +2 -0
  97. package/dist/web/RaftUtils.js +22 -2
  98. package/dist/web/RaftUtils.js.map +1 -1
  99. package/dist/web/main.d.ts +2 -0
  100. package/dist/web/main.js +4 -1
  101. package/dist/web/main.js.map +1 -1
  102. package/examples/dashboard/package.json +1 -1
  103. package/examples/dashboard/src/CommandPanel.tsx +3 -3
  104. package/examples/dashboard/src/ConnManager.ts +83 -6
  105. package/examples/dashboard/src/DeviceActionsForm.tsx +2 -2
  106. package/examples/dashboard/src/DevicePanel.tsx +2 -2
  107. package/examples/dashboard/src/Main.tsx +14 -4
  108. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +4 -4
  109. package/examples/dashboard/src/styles.css +8 -0
  110. package/examples/dashboard/tsconfig.json +1 -1
  111. package/package.json +10 -11
  112. package/src/RaftAttributeHandler.ts +163 -11
  113. package/src/RaftChannel.ts +2 -0
  114. package/src/RaftChannelBLE.native.ts +17 -8
  115. package/src/RaftChannelBLE.web.ts +28 -16
  116. package/src/RaftChannelSimulated.ts +482 -0
  117. package/src/RaftChannelWebSerial.ts +18 -7
  118. package/src/RaftChannelWebSocket.ts +13 -0
  119. package/src/RaftConnector.ts +13 -7
  120. package/src/RaftCustomAttrHandler.ts +17 -0
  121. package/src/RaftDeviceInfo.ts +8 -2
  122. package/src/RaftDeviceManager.ts +155 -47
  123. package/src/RaftFileHandler.ts +8 -8
  124. package/src/RaftLog.ts +1 -1
  125. package/src/RaftMsgHandler.ts +36 -0
  126. package/src/RaftStreamHandler.ts +48 -47
  127. package/src/RaftStruct.ts +1 -1
  128. package/src/RaftSysTypeManager.ts +27 -0
  129. package/src/RaftSystemType.ts +9 -7
  130. package/src/RaftSystemUtils.ts +3 -1
  131. package/src/RaftUpdateManager.ts +2 -2
  132. package/src/RaftUtils.ts +25 -5
  133. package/src/main.ts +2 -0
package/dist/web/main.js CHANGED
@@ -9,7 +9,7 @@
9
9
  //
10
10
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.RaftDeviceManager = exports.RaftSysTypeManager = exports.RaftUtils = exports.RaftSystemUtils = exports.RaftStreamHandler = exports.RaftMsgHandler = exports.RaftMiniHDLC = exports.RaftLog = exports.RaftFileHandler = exports.RaftChannelWebSocket = exports.RaftConnector = exports.RaftCommsStats = exports.RaftChannelBLE = void 0;
12
+ exports.RaftDeviceManager = exports.RaftSysTypeManager = exports.RaftUtils = exports.RaftSystemUtils = exports.RaftStreamHandler = exports.RaftMsgHandler = exports.RaftMiniHDLC = exports.RaftLog = exports.RaftFileHandler = exports.RaftChannelSimulated = exports.RaftChannelWebSocket = exports.RaftConnector = exports.RaftCommsStats = exports.RaftChannelBLE = void 0;
13
13
  const tslib_1 = require("tslib");
14
14
  const RaftChannelBLEFactory_1 = require("./RaftChannelBLEFactory");
15
15
  const raftChannel = (0, RaftChannelBLEFactory_1.createBLEChannel)();
@@ -20,6 +20,8 @@ var RaftConnector_1 = require("./RaftConnector");
20
20
  Object.defineProperty(exports, "RaftConnector", { enumerable: true, get: function () { return tslib_1.__importDefault(RaftConnector_1).default; } });
21
21
  var RaftChannelWebSocket_1 = require("./RaftChannelWebSocket");
22
22
  Object.defineProperty(exports, "RaftChannelWebSocket", { enumerable: true, get: function () { return tslib_1.__importDefault(RaftChannelWebSocket_1).default; } });
23
+ var RaftChannelSimulated_1 = require("./RaftChannelSimulated");
24
+ Object.defineProperty(exports, "RaftChannelSimulated", { enumerable: true, get: function () { return tslib_1.__importDefault(RaftChannelSimulated_1).default; } });
23
25
  var RaftFileHandler_1 = require("./RaftFileHandler");
24
26
  Object.defineProperty(exports, "RaftFileHandler", { enumerable: true, get: function () { return tslib_1.__importDefault(RaftFileHandler_1).default; } });
25
27
  var RaftLog_1 = require("./RaftLog");
@@ -45,4 +47,5 @@ tslib_1.__exportStar(require("./RaftConnEvents"), exports);
45
47
  tslib_1.__exportStar(require("./RaftUpdateEvents"), exports);
46
48
  tslib_1.__exportStar(require("./RaftProtocolDefs"), exports);
47
49
  tslib_1.__exportStar(require("./RaftDeviceStates"), exports);
50
+ tslib_1.__exportStar(require("./RaftDeviceInfo"), exports);
48
51
  //# sourceMappingURL=main.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";AAAA,iHAAiH;AACjH,EAAE;AACF,SAAS;AACT,gGAAgG;AAChG,EAAE;AACF,wCAAwC;AACxC,oCAAoC;AACpC,EAAE;AACF,iHAAiH;;;;AAEjH,mEAA2D;AAC3D,MAAM,WAAW,GAAG,IAAA,wCAAgB,GAAE,CAAC;AACf,qCAAc;AAEtC,mDAA6D;AAApD,yIAAA,OAAO,OAAkB;AAClC,iDAA2D;AAAlD,uIAAA,OAAO,OAAiB;AAEjC,+DAAyE;AAAhE,qJAAA,OAAO,OAAwB;AACxC,qDAA+D;AAAtD,2IAAA,OAAO,OAAmB;AACnC,qCAA+C;AAAtC,2HAAA,OAAO,OAAW;AAC3B,+CAAyD;AAAhD,qIAAA,OAAO,OAAgB;AAChC,mDAA4D;AAAnD,yIAAA,OAAO,OAAkB;AAClC,yDAAmE;AAA1D,+IAAA,OAAO,OAAqB;AACrC,qDAA+D;AAAtD,2IAAA,OAAO,OAAmB;AACnC,yCAAmD;AAA1C,+HAAA,OAAO,OAAa;AAC7B,2DAAqE;AAA5D,iJAAA,OAAO,OAAsB;AAEtC,yDAAyE;AAAhE,sHAAA,aAAa,OAAqB;AAE3C,sDAA4B;AAC5B,2DAAiC;AACjC,0DAAgC;AAChC,2DAAiC;AACjC,6DAAmC;AACnC,6DAAmC;AACnC,6DAAmC"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";AAAA,iHAAiH;AACjH,EAAE;AACF,SAAS;AACT,gGAAgG;AAChG,EAAE;AACF,wCAAwC;AACxC,oCAAoC;AACpC,EAAE;AACF,iHAAiH;;;;AAEjH,mEAA2D;AAC3D,MAAM,WAAW,GAAG,IAAA,wCAAgB,GAAE,CAAC;AACf,qCAAc;AAEtC,mDAA6D;AAApD,yIAAA,OAAO,OAAkB;AAClC,iDAA2D;AAAlD,uIAAA,OAAO,OAAiB;AAEjC,+DAAyE;AAAhE,qJAAA,OAAO,OAAwB;AACxC,+DAAyE;AAAhE,qJAAA,OAAO,OAAwB;AACxC,qDAA+D;AAAtD,2IAAA,OAAO,OAAmB;AACnC,qCAA+C;AAAtC,2HAAA,OAAO,OAAW;AAC3B,+CAAyD;AAAhD,qIAAA,OAAO,OAAgB;AAChC,mDAA4D;AAAnD,yIAAA,OAAO,OAAkB;AAClC,yDAAmE;AAA1D,+IAAA,OAAO,OAAqB;AACrC,qDAA+D;AAAtD,2IAAA,OAAO,OAAmB;AACnC,yCAAmD;AAA1C,+HAAA,OAAO,OAAa;AAC7B,2DAAqE;AAA5D,iJAAA,OAAO,OAAsB;AAEtC,yDAAyE;AAAhE,sHAAA,aAAa,OAAqB;AAE3C,sDAA4B;AAC5B,2DAAiC;AACjC,0DAAgC;AAChC,2DAAiC;AACjC,6DAAmC;AACnC,6DAAmC;AACnC,6DAAmC;AACnC,2DAAiC"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raftjs-example-dashboard",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "start": "npm run clean && parcel build src/index.html && parcel src/index.html",
@@ -38,17 +38,17 @@ export default function CommandPanel() {
38
38
 
39
39
  // Update history only if the command is not the same as the last one
40
40
  if (commandHistory.length === 0 || commandHistory[commandHistory.length - 1] !== cmd) {
41
- setCommandHistory((prevHistory) => [...prevHistory, cmd]);
41
+ setCommandHistory((prevHistory: any) => [...prevHistory, cmd]);
42
42
  }
43
43
 
44
44
  // Reset the history index and clear the command
45
45
  setHistoryIndex(-1);
46
46
  setCommand('');
47
47
  }).catch(error => {
48
- console.error(`Error sending command: ${cmd}`, error);
48
+ console.warn(`Error sending command: ${cmd}`, error);
49
49
  });
50
50
  } else {
51
- console.error("Command is empty.");
51
+ console.warn("Command is empty.");
52
52
  }
53
53
  };
54
54
 
@@ -20,7 +20,9 @@ export default class ConnManager {
20
20
  private _connector = new RaftConnector(async (systemUtils: RaftSystemUtils) => {
21
21
  const systemInfo = await systemUtils.getSystemInfo();
22
22
  const sysType = sysTypeManager.createSystemType(systemInfo.SystemName) || sysTypeManager.createDefaultSystemType();
23
- sysType?.deviceMgrIF.setMaxDataPointsToStore(settingsManager.getSetting("maxDatapointsToStore"));
23
+ if (sysType && sysType.deviceMgrIF) {
24
+ sysType.deviceMgrIF.setMaxDataPointsToStore(settingsManager.getSetting("maxDatapointsToStore"));
25
+ }
24
26
  return sysType;
25
27
  });
26
28
 
@@ -49,8 +51,22 @@ export default class ConnManager {
49
51
  return this._connector;
50
52
  }
51
53
 
52
- private async getBleDevice(uuids: string[]): Promise<BluetoothDevice | null> {
53
- const filtersArray = uuids.map((uuid) => ({ services: [uuid] }));
54
+ private async getBleDevice(uuids: string[], serialNo: string | null = null): Promise<BluetoothDevice | null> {
55
+
56
+ // Filter by main service UUID if no serial number provided
57
+ let filtersArray = uuids.map((uuid) => ({ services: [ uuid] }));
58
+
59
+ // Check if a serial number is provided
60
+ if ((serialNo !== null) && (serialNo !== "")) {
61
+
62
+ // Generate a UUID from the base UUID xored with serial number in BCD form
63
+ const baseUUID = "aa76677e-9cfd-4626-0000-000000000000";
64
+ const modifiedUUID = this.generateServiceFilterUUID(baseUUID, serialNo);
65
+ filtersArray = [{ services: [modifiedUUID] }];
66
+
67
+ // console.log(`getBleDevice - modified UUID: ${modifiedUUID}`);
68
+ }
69
+
54
70
  try {
55
71
  const dev = await navigator.bluetooth.requestDevice({
56
72
  filters: filtersArray,
@@ -58,13 +74,13 @@ export default class ConnManager {
58
74
  });
59
75
  return dev;
60
76
  } catch (e) {
61
- RaftLog.error(`getBleDevice - failed to get device ${e}`);
77
+ RaftLog.warn(`getBleDevice - failed to get device ${e}`);
62
78
  return null;
63
79
  }
64
80
  }
65
81
 
66
82
  // Connect
67
- public async connect(method: string, locator: string | object, uuids: string[]): Promise<boolean> {
83
+ public async connect(method: string, locator: string | object, uuids: string[], serialNo: string | null = null): Promise<boolean> {
68
84
 
69
85
  // Hook up the connector
70
86
  this._connector.setEventListener((evtType, eventEnum, eventName, eventData) => {
@@ -76,7 +92,7 @@ export default class ConnManager {
76
92
  await this._connector.initializeChannel(method);
77
93
  // Set the connector websocket suffix
78
94
  if (method === "WebBLE") {
79
- const dev = await this.getBleDevice(uuids);
95
+ const dev = await this.getBleDevice(uuids, serialNo);
80
96
  return this._connector.connect(dev as object);
81
97
  }
82
98
  return this._connector.connect(locator);
@@ -86,4 +102,65 @@ export default class ConnManager {
86
102
  public disconnect(): Promise<void> {
87
103
  return this._connector.disconnect();
88
104
  }
105
+
106
+ ///////////////////////////////////////////////////////////////////////////////////
107
+ /// @brief Generate a UUID for service filtering based on device serial number
108
+ /// @param baseUUID Base UUID string (e.g., "aa76677e-9cfd-4626-0000-000000000000")
109
+ /// @param serialNo Serial number as an ASCII string (e.g., "1234567890123456")
110
+ /// @returns Modified UUID string
111
+ public generateServiceFilterUUID(baseUUID: string, serialNo: string): string {
112
+ const UUID_128_BYTES = 16;
113
+
114
+ // Convert UUID string to byte array
115
+ let uuidBytes = this.uuidToByteArray(baseUUID);
116
+
117
+ // Convert serial number assuming it is decimal (or hex) digits to bytes
118
+ let serialBytes = this.hexStringToBytes(serialNo);
119
+
120
+ // Limit to 16 bytes (UUID size)
121
+ const bytesToProc = Math.min(serialBytes.length, UUID_128_BYTES);
122
+
123
+ // console.log(`generateServiceFilterUUID - serialBCD: ${serialBCD} bytesToProc: ${bytesToProc}`);
124
+
125
+ // XOR the serial BCD bytes with the UUID bytes
126
+ for (let i = 0; i < bytesToProc; i++) {
127
+ uuidBytes[15 - i] ^= serialBytes[bytesToProc - 1 - i];
128
+ }
129
+
130
+ // Convert back to UUID string format
131
+ return this.byteArrayToUUID(uuidBytes);
132
+ }
133
+
134
+ /////////////////////////////////////////////////////////////////////////////
135
+ /// @brief Convert UUID string to byte array (Big Endian order)
136
+ public uuidToByteArray(uuid: string): Uint8Array {
137
+ return new Uint8Array(
138
+ uuid.replace(/-/g, "") // Remove dashes
139
+ .match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)) // Convert hex pairs to bytes
140
+ );
141
+ }
142
+
143
+ /////////////////////////////////////////////////////////////////////////////
144
+ /// @brief Convert byte array back to UUID string
145
+ public byteArrayToUUID(bytes: Uint8Array): string {
146
+ return [...bytes]
147
+ .map(b => b.toString(16).padStart(2, "0")) // Convert to hex
148
+ .join("")
149
+ .replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5"); // Format as UUID
150
+ }
151
+
152
+ /////////////////////////////////////////////////////////////////////////////
153
+ /// @brief Convert an hex string to bytes
154
+ /// @param hex string - e.g. "1234567890123456" -> [0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56]
155
+ /// @returns byte array
156
+ public hexStringToBytes(hex: string): Uint8Array {
157
+ // Pad to ensure even number of characters
158
+ if (hex.length % 2 !== 0) {
159
+ hex = "0" + hex;
160
+ }
161
+
162
+ return new Uint8Array(
163
+ hex.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || []
164
+ );
165
+ }
89
166
  }
@@ -13,7 +13,7 @@ interface InputValues {
13
13
  [key: string]: number;
14
14
  }
15
15
 
16
- const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }) => {
16
+ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: DeviceActionsTableProps) => {
17
17
  const deviceManager = connManager.getConnector().getSystemType()?.deviceMgrIF;
18
18
  const [deviceActions, setDeviceActions] = useState<DeviceTypeAction[]>([]);
19
19
  const [inputValues, setInputValues] = useState<InputValues>({});
@@ -44,7 +44,7 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }) =>
44
44
  }, [deviceKey]);
45
45
 
46
46
  const handleInputChange = (name: string, value: number) => {
47
- setInputValues((prevValues) => ({
47
+ setInputValues((prevValues: any) => ({
48
48
  ...prevValues,
49
49
  [name]: value,
50
50
  }));
@@ -83,7 +83,7 @@ const DevicePanel = ({ deviceKey, lastUpdated }: DevicePanelProps) => {
83
83
  navigator.clipboard.writeText(csvContent).then(() => {
84
84
  console.log("Device values copied to clipboard");
85
85
  }).catch(err => {
86
- console.error('Failed to copy: ', err);
86
+ console.warn('Failed to copy: ', err);
87
87
  fallbackCopyTextToClipboard(csvContent);
88
88
  });
89
89
  } else {
@@ -109,7 +109,7 @@ const DevicePanel = ({ deviceKey, lastUpdated }: DevicePanelProps) => {
109
109
  document.execCommand("copy");
110
110
  // alert("Device values copied to clipboard!");
111
111
  } catch (err) {
112
- console.error('Fallback: Oops, unable to copy', err);
112
+ console.warn('Fallback: Oops, unable to copy', err);
113
113
  alert("Failed to copy device values to clipboard");
114
114
  }
115
115
 
@@ -35,12 +35,14 @@ export default function Main() {
35
35
  localStorage.getItem('lastIpAddress') || ''
36
36
  );
37
37
 
38
+ const [serialNo, setSerialNo] = useState<string>('');
39
+
38
40
  const handleConnect = () => {
39
41
  if (ipAddress.trim() === '') {
40
- console.error('No IP address entered');
42
+ console.warn('No IP address entered');
41
43
  return;
42
44
  }
43
- connManager.connect('WebSocket', ipAddress, []);
45
+ connManager.connect('WebSocket', ipAddress, [], null);
44
46
  localStorage.setItem('lastIpAddress', ipAddress);
45
47
  };
46
48
 
@@ -193,10 +195,18 @@ export default function Main() {
193
195
  </div>
194
196
  <div className="info-box">
195
197
  <h3>WebBLE</h3>
198
+ <input
199
+ className="serial-no-input"
200
+ id="serial-no"
201
+ type="text"
202
+ placeholder="Serial No (ignored if empty)"
203
+ value={serialNo}
204
+ onChange={(e) => setSerialNo(e.target.value)}
205
+ />
196
206
  <button
197
207
  className="action-button"
198
208
  onClick={() => {
199
- connManager.connect('WebBLE', '', sysTypeManager.getAllServiceUUIDs());
209
+ connManager.connect('WebBLE', '', sysTypeManager.getAllServiceUUIDs(), serialNo);
200
210
  }}
201
211
  >
202
212
  Connect
@@ -207,7 +217,7 @@ export default function Main() {
207
217
  <button
208
218
  className="action-button"
209
219
  onClick={() => {
210
- connManager.connect('WebSerial', '', []);
220
+ connManager.connect('WebSerial', '', [], null);
211
221
  }}
212
222
  >
213
223
  Connect
@@ -98,7 +98,7 @@ export default class RICSystemUtils {
98
98
  const retrieveResult = await this.retrieveInfo();
99
99
  return retrieveResult;
100
100
  } catch (err) {
101
- RaftLog.error(`retrieveMartySystemInfo: error ${err}`);
101
+ RaftLog.warn(`retrieveMartySystemInfo: error ${err}`);
102
102
  }
103
103
  return false;
104
104
  }
@@ -260,7 +260,7 @@ export default class RICSystemUtils {
260
260
  // Debug
261
261
  RaftLog.debug(
262
262
  `getHWElemList: found ${hwElemList.hw.length} addons/buspixels`
263
-
263
+ );
264
264
  } else if (addToNonAddOnsList) {
265
265
  this._hwElemsExcludingAddOns.push(...hwElemList.hw);
266
266
  // Debug
@@ -279,9 +279,9 @@ export default class RICSystemUtils {
279
279
  try {
280
280
  const reports: Array<RaftReportMsg> = [];
281
281
  // add callback to subscribe to report messages
282
- this._msgHandler.reportMsgCallbacksSet("getHWElemCB", function (
282
+ this._msgHandler.reportMsgCallbacksSet("getHWElemCB", async function (
283
283
  report: RaftReportMsg
284
- ) {
284
+ ): Promise<void> {
285
285
  reports.push(report);
286
286
  RaftLog.debug(`getHWElemCB Report callback ${JSON.stringify(report)}`);
287
287
  });
@@ -121,6 +121,14 @@ h1 {
121
121
  width: 100%;
122
122
  }
123
123
 
124
+ .serial-no-input {
125
+ border: 1px solid #ccc;
126
+ border-radius: 4px;
127
+ margin-bottom: 10px;
128
+ padding: 10px;
129
+ width: 100%;
130
+ }
131
+
124
132
  .command-input {
125
133
  padding: 10px;
126
134
  margin-bottom: 10px;
@@ -4,7 +4,7 @@
4
4
  "esModuleInterop": true,
5
5
  "resolveJsonModule": true,
6
6
  "module": "esnext",
7
- "target": "es5",
7
+ "target": "es2017",
8
8
  "strict": true,
9
9
  "moduleResolution": "node",
10
10
  "lib": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robotical/raftjs",
3
- "version": "1.4.7",
3
+ "version": "2.0.3",
4
4
  "description": "Javascript/TS library for Raft library",
5
5
  "main": "dist/web/main.js",
6
6
  "types": "dist/web/main.d.ts",
@@ -31,19 +31,18 @@
31
31
  "watch-all": "tsc -p tsconfig.json --watch & tsc -p tsconfig.react-native.json --watch"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/node": "^20.14.0",
35
- "@types/web-bluetooth": "^0.0.20",
36
- "@typescript-eslint/eslint-plugin": "^8.6.0",
37
- "eslint": "^9.4.0",
38
- "react-native-ble-plx": "^3.2.0",
39
- "typescript": "^5.4.5",
40
- "@types/text-encoding": "^0.0.39",
41
- "rimraf": "^6.0.1"
34
+ "@types/node": "^22.13.11",
35
+ "@types/web-bluetooth": "^0.0.21",
36
+ "@typescript-eslint/eslint-plugin": "^8.27.0",
37
+ "eslint": "^9.23.0",
38
+ "react-native-ble-plx": "^3.5.0",
39
+ "typescript": "^5.8.2",
40
+ "rimraf": "^6.0.1",
41
+ "@types/text-encoding": "^0.0.40"
42
42
  },
43
43
  "dependencies": {
44
- "text-encoding": "^0.7.0",
45
44
  "isomorphic-ws": "^5.0.0",
46
- "tslib": "^2.6.2"
45
+ "tslib": "^2.8.1"
47
46
  },
48
47
  "peerDependencies": {
49
48
  "react-native-ble-plx": "*",
@@ -45,6 +45,8 @@ export default class AttributeHandler {
45
45
 
46
46
  } else {
47
47
 
48
+ // console.log(`RaftAttrHdlr.processMsgAttrGroup ${JSON.stringify(pollRespMetadata)} msgBufIdx ${msgBufIdx} timestampUs ${timestampUs}`);
49
+
48
50
  // Iterate over attributes
49
51
  for (let attrIdx = 0; attrIdx < pollRespMetadata.a.length; attrIdx++) {
50
52
 
@@ -56,6 +58,8 @@ export default class AttributeHandler {
56
58
  continue;
57
59
  }
58
60
 
61
+ // console.log(`RaftAttrHdlr.processMsgAttrGroup attr ${attrDef.n} msgBufIdx ${msgBufIdx} timestampUs ${timestampUs} attrDef ${JSON.stringify(attrDef)}`);
62
+
59
63
  // Process the attribute
60
64
  const { values, newMsgBufIdx } = this.processMsgAttribute(attrDef, msgBuffer, msgBufIdx, msgDataStartIdx);
61
65
  if (newMsgBufIdx < 0) {
@@ -65,7 +69,6 @@ export default class AttributeHandler {
65
69
  msgBufIdx = newMsgBufIdx;
66
70
  newAttrValues.push(values);
67
71
  }
68
-
69
72
  }
70
73
 
71
74
  // Number of bytes in group
@@ -142,19 +145,94 @@ export default class AttributeHandler {
142
145
  // Add the new timestamps
143
146
  deviceTimeline.timestampsUs.push(...timestampsUs);
144
147
 
148
+ // Validate attributes based on the vft field
149
+ this.validateAttributes(pollRespMetadata, devAttrsState, numNewDataPoints);
150
+
145
151
  // Return the next message buffer index
146
152
  return msgDataStartIdx+pollRespSizeBytes;
147
153
  }
148
154
 
155
+ private validateAttributes(pollRespMetadata: DeviceTypePollRespMetadata, devAttrsState: DeviceAttributesState, numNewDataPoints: number): void {
156
+ // Iterate through all attributes to find those with a vft field
157
+ for (let attrIdx = 0; attrIdx < pollRespMetadata.a.length; attrIdx++) {
158
+ const attrDef: DeviceTypeAttribute = pollRespMetadata.a[attrIdx];
159
+
160
+ // Check if this attribute has a vft field
161
+ if (!("vft" in attrDef) || !attrDef.vft) {
162
+ continue;
163
+ }
164
+
165
+ // Get the name of the validating attribute
166
+ const validatingAttrName = attrDef.vft;
167
+
168
+ // Check if the validating attribute exists in the state
169
+ if (!(validatingAttrName in devAttrsState)) {
170
+ console.debug(`Cannot validate attribute ${attrDef.n} as validating attribute ${validatingAttrName} doesn't exist`);
171
+ continue;
172
+ }
173
+
174
+ // Get the current attribute state
175
+ const currentAttr = devAttrsState[attrDef.n];
176
+ const validatingAttr = devAttrsState[validatingAttrName];
177
+
178
+ // Check if both attributes have values
179
+ if (!currentAttr.values.length || !validatingAttr.values.length) {
180
+ continue;
181
+ }
182
+
183
+ // Get the most recent values from both attributes
184
+ const numValues = currentAttr.values.length;
185
+ const startIdx = numValues - numNewDataPoints;
186
+
187
+ // Process each of the new values
188
+ for (let i = 0; i < numNewDataPoints; i++) {
189
+ const valueIdx = startIdx + i;
190
+ if (valueIdx >= 0 && valueIdx < numValues) {
191
+ // Check if the validating attribute's value is 0/false at the same index
192
+ const validatingValueIdx = validatingAttr.values.length - numNewDataPoints + i;
193
+ if (validatingValueIdx >= 0 && validatingValueIdx < validatingAttr.values.length) {
194
+ // If the validating attribute's value is 0 or false, mark the current value as invalid
195
+ if (!validatingAttr.values[validatingValueIdx]) {
196
+ currentAttr.values[valueIdx] = NaN; // Using NaN to represent invalid values
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+
149
204
  private processMsgAttribute(attrDef: DeviceTypeAttribute, msgBuffer: Uint8Array, msgBufIdx: number, msgDataStartIdx: number): { values: number[], newMsgBufIdx: number} {
150
205
 
151
206
  // Current field message string index
152
207
  let curFieldBufIdx = msgBufIdx;
153
208
  let attrUsesAbsPos = false;
154
209
 
155
- // Check for "at": N which means start reading from byte N of the message (after the timestamp bytes)
210
+ // Check for "at" field which means absolute position in the buffer
156
211
  if (attrDef.at !== undefined) {
157
- curFieldBufIdx = msgDataStartIdx + attrDef.at;
212
+ // Handle both single value and array of byte positions
213
+ if (Array.isArray(attrDef.at)) {
214
+ // Create a new buffer for non-contiguous data extraction
215
+ const elemSize = structSizeOf(attrDef.t);
216
+ const bytesForType = new Uint8Array(elemSize);
217
+
218
+ // Zero out the buffer
219
+ bytesForType.fill(0);
220
+
221
+ // Copy bytes from the specified positions
222
+ for (let i = 0; i < attrDef.at.length && i < elemSize; i++) {
223
+ const sourceIdx = msgDataStartIdx + attrDef.at[i];
224
+ if (sourceIdx < msgBuffer.length) {
225
+ bytesForType[i] = msgBuffer[sourceIdx];
226
+ }
227
+ }
228
+
229
+ // Use this buffer for attribute extraction
230
+ msgBuffer = bytesForType;
231
+ curFieldBufIdx = 0;
232
+ } else {
233
+ // Standard absolute position in the buffer
234
+ curFieldBufIdx = msgDataStartIdx + attrDef.at;
235
+ }
158
236
  attrUsesAbsPos = true;
159
237
  }
160
238
 
@@ -194,13 +272,13 @@ export default class AttributeHandler {
194
272
  // Check for XOR mask
195
273
  if ("x" in attrDef) {
196
274
  const mask = typeof attrDef.x === "string" ? parseInt(attrDef.x, 16) : attrDef.x as number;
197
- attrValues = attrValues.map((value) => value ^ mask);
275
+ attrValues = attrValues.map((value) => (value >>> 0) ^ mask);
198
276
  }
199
277
 
200
278
  // Check for AND mask
201
279
  if ("m" in attrDef) {
202
280
  const mask = typeof attrDef.m === "string" ? parseInt(attrDef.m, 16) : attrDef.m as number;
203
- attrValues = attrValues.map((value) => (maskOnSignedValue ? this.signExtend(value, mask) : value & mask));
281
+ attrValues = attrValues.map((value) => (maskOnSignedValue ? this.signExtend(value, mask) : (value >>> 0) & mask));
204
282
  }
205
283
 
206
284
  // Check for a sign-bit
@@ -219,9 +297,9 @@ export default class AttributeHandler {
219
297
  if ("s" in attrDef && attrDef.s) {
220
298
  const bitshift = attrDef.s as number;
221
299
  if (bitshift > 0) {
222
- attrValues = attrValues.map((value) => (value) >> bitshift);
300
+ attrValues = attrValues.map((value) => (value >>> 0) >>> bitshift);
223
301
  } else if (bitshift < 0) {
224
- attrValues = attrValues.map((value) => (value) << -bitshift);
302
+ attrValues = attrValues.map((value) => (value >>> 0) << -bitshift);
225
303
  }
226
304
  }
227
305
 
@@ -237,10 +315,47 @@ export default class AttributeHandler {
237
315
  attrValues = attrValues.map((value) => (value) + addValue);
238
316
  }
239
317
 
240
- // console.log(`DeviceManager msg attrGroup ${attrGroup} devkey ${deviceKey} valueHexChars ${valueHexChars} msgHexStr ${msgHexStr} ts ${timestamp} attrName ${attrDef.n} type ${attrDef.t} value ${value} signExtendableMaskSignPos ${signExtendableMaskSignPos} attrTypeDefForStruct ${attrTypeDefForStruct} attrDef ${attrDef}`);
318
+ // Apply lookup table if defined
319
+ if ("lut" in attrDef && attrDef.lut !== undefined) {
320
+ attrValues = attrValues.map((value): number => {
321
+ // Skip NaN values
322
+ if (isNaN(value)) {
323
+ return value;
324
+ }
325
+
326
+ // Search through the lookup table rows for a match
327
+ let defaultValue: number | null = null;
328
+
329
+ for (const row of attrDef.lut || []) {
330
+ // Empty string means default for unmatched values
331
+ if (row.r === "") {
332
+ defaultValue = row.v;
333
+ continue;
334
+ }
335
+
336
+ // Parse the range string
337
+ if (this.isValueInRangeString(value, row.r)) {
338
+ return row.v;
339
+ }
340
+ }
341
+
342
+ // If no match found but we have a default, use it
343
+ if (defaultValue !== null) {
344
+ return defaultValue;
345
+ }
346
+
347
+ // Otherwise keep the original value
348
+ return value;
349
+ });
350
+ }
351
+
352
+ // const msgBufIdxIn = msgBufIdx;
353
+
241
354
  // Move buffer position if using relative positioning
242
355
  msgBufIdx += attrUsesAbsPos ? 0 : numBytesConsumed;
243
356
 
357
+ // console.log(`RaftAttrHdlr.processMsgAttr attr ${attrDef.n} msgBufIdx ${msgBufIdxIn} msgBufIdx ${msgBufIdx} attrUsesAbsPos ${attrUsesAbsPos} numBytesConsumed ${numBytesConsumed} attrValues ${attrValues}`);
358
+
244
359
  // if (attrDef.n === "amb0") {
245
360
  // console.log(`${new Date().toISOString()} ${attrDef.n} ${attrValues}`);
246
361
  // }
@@ -264,7 +379,7 @@ export default class AttributeHandler {
264
379
  private extractTimestampAndAdvanceIdx(msgBuffer: Uint8Array, msgBufIdx: number, timestampWrapHandler: DeviceTimeline):
265
380
  { newBufIdx: number, timestampUs: number } {
266
381
 
267
- // Check there are enough characters for the timestamp
382
+ // Check there are enough bytes for the timestamp
268
383
  if (msgBufIdx + this.POLL_RESULT_TIMESTAMP_SIZE > msgBuffer.length) {
269
384
  return { newBufIdx: -1, timestampUs: 0 };
270
385
  }
@@ -278,8 +393,8 @@ export default class AttributeHandler {
278
393
  timestampUs = structUnpack(">I", tsBuffer)[0] as number * this.POLL_RESULT_RESOLUTION_US;
279
394
  }
280
395
 
281
- // Check if time is before lastReportTimeMs - in which case a wrap around occurred to add on the max value
282
- if (timestampUs < timestampWrapHandler.lastReportTimestampUs) {
396
+ // Check if time is before lastReportTimeMs by more than 100ms - in which case a wrap around occurred to add on the max value
397
+ if (timestampUs + 100000 < timestampWrapHandler.lastReportTimestampUs ) {
283
398
  timestampWrapHandler.reportTimestampOffsetUs += this.POLL_RESULT_WRAP_VALUE * this.POLL_RESULT_RESOLUTION_US;
284
399
  }
285
400
  timestampWrapHandler.lastReportTimestampUs = timestampUs;
@@ -294,5 +409,42 @@ export default class AttributeHandler {
294
409
  return { newBufIdx: msgBufIdx, timestampUs: timestampUs };
295
410
  }
296
411
 
412
+ // Helper method to check if a value is in a range string like "42,43,44-45,47"
413
+ private isValueInRangeString(value: number, rangeStr: string): boolean {
414
+ // Round to integer for comparison
415
+ const roundedValue = Math.round(value);
416
+
417
+ // Split the range string by commas
418
+ const parts = rangeStr.split(',');
419
+
420
+ for (const part of parts) {
421
+ // Check if it's a range (contains a hyphen)
422
+ if (part.includes('-')) {
423
+ const [startStr, endStr] = part.split('-');
424
+
425
+ // Handle hex values
426
+ const start = startStr.toLowerCase().startsWith('0x') ?
427
+ parseInt(startStr, 16) : parseInt(startStr, 10);
428
+ const end = endStr.toLowerCase().startsWith('0x') ?
429
+ parseInt(endStr, 16) : parseInt(endStr, 10);
430
+
431
+ if (!isNaN(start) && !isNaN(end) && roundedValue >= start && roundedValue <= end) {
432
+ return true;
433
+ }
434
+ }
435
+ // Check if it's a single value
436
+ else {
437
+ // Handle hex values
438
+ const partValue = part.toLowerCase().startsWith('0x') ?
439
+ parseInt(part, 16) : parseInt(part, 10);
440
+
441
+ if (!isNaN(partValue) && roundedValue === partValue) {
442
+ return true;
443
+ }
444
+ }
445
+ }
446
+
447
+ return false;
448
+ }
297
449
 
298
450
  }
@@ -22,6 +22,8 @@ export default interface RaftChannel
22
22
  setMsgHandler(raftMsgHandler: RaftMsgHandler): void;
23
23
  sendTxMsg(msg: Uint8Array, sendWithResponse: boolean): Promise<boolean>;
24
24
  sendTxMsgNoAwait(msg: Uint8Array, sendWithResponse: boolean): Promise<boolean>;
25
+ sendTxMsgRaw(msg: string): boolean;
26
+ sendTxMsgRawAndWaitForReply<T>(msgPayload: Uint8Array): T;
25
27
  requiresSubscription(): boolean;
26
28
  ricRestCmdBeforeDisconnect(): string | null;
27
29
  fhBatchAckSize(): number;