@robdobsn/raftjs 1.10.7 → 1.11.6

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 (89) hide show
  1. package/dist/react-native/RaftAttributeHandler.js +11 -9
  2. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  3. package/dist/react-native/RaftChannelSimulated.js +4 -3
  4. package/dist/react-native/RaftChannelSimulated.js.map +1 -1
  5. package/dist/react-native/RaftConnector.d.ts +10 -1
  6. package/dist/react-native/RaftConnector.js +23 -10
  7. package/dist/react-native/RaftConnector.js.map +1 -1
  8. package/dist/react-native/RaftDeviceManager.d.ts +14 -2
  9. package/dist/react-native/RaftDeviceManager.js +224 -77
  10. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  11. package/dist/react-native/RaftDeviceMgrIF.d.ts +5 -1
  12. package/dist/react-native/RaftDeviceStates.d.ts +21 -3
  13. package/dist/react-native/RaftDeviceStates.js +31 -6
  14. package/dist/react-native/RaftDeviceStates.js.map +1 -1
  15. package/dist/react-native/RaftPublish.d.ts +2 -0
  16. package/dist/react-native/RaftPublish.js +81 -0
  17. package/dist/react-native/RaftPublish.js.map +1 -0
  18. package/dist/react-native/RaftStreamHandler.d.ts +11 -0
  19. package/dist/react-native/RaftStreamHandler.js +66 -0
  20. package/dist/react-native/RaftStreamHandler.js.map +1 -1
  21. package/dist/react-native/RaftStruct.d.ts +2 -2
  22. package/dist/react-native/RaftStruct.js +97 -26
  23. package/dist/react-native/RaftStruct.js.map +1 -1
  24. package/dist/react-native/RaftSystemUtils.d.ts +17 -1
  25. package/dist/react-native/RaftSystemUtils.js +51 -0
  26. package/dist/react-native/RaftSystemUtils.js.map +1 -1
  27. package/dist/react-native/RaftTypes.d.ts +21 -0
  28. package/dist/react-native/RaftTypes.js.map +1 -1
  29. package/dist/react-native/main.d.ts +1 -0
  30. package/dist/react-native/main.js +1 -0
  31. package/dist/react-native/main.js.map +1 -1
  32. package/dist/web/RaftAttributeHandler.js +11 -9
  33. package/dist/web/RaftAttributeHandler.js.map +1 -1
  34. package/dist/web/RaftChannelSimulated.js +4 -3
  35. package/dist/web/RaftChannelSimulated.js.map +1 -1
  36. package/dist/web/RaftConnector.d.ts +10 -1
  37. package/dist/web/RaftConnector.js +23 -10
  38. package/dist/web/RaftConnector.js.map +1 -1
  39. package/dist/web/RaftDeviceManager.d.ts +14 -2
  40. package/dist/web/RaftDeviceManager.js +224 -77
  41. package/dist/web/RaftDeviceManager.js.map +1 -1
  42. package/dist/web/RaftDeviceMgrIF.d.ts +5 -1
  43. package/dist/web/RaftDeviceStates.d.ts +21 -3
  44. package/dist/web/RaftDeviceStates.js +31 -6
  45. package/dist/web/RaftDeviceStates.js.map +1 -1
  46. package/dist/web/RaftPublish.d.ts +2 -0
  47. package/dist/web/RaftPublish.js +81 -0
  48. package/dist/web/RaftPublish.js.map +1 -0
  49. package/dist/web/RaftStreamHandler.d.ts +11 -0
  50. package/dist/web/RaftStreamHandler.js +66 -0
  51. package/dist/web/RaftStreamHandler.js.map +1 -1
  52. package/dist/web/RaftStruct.d.ts +2 -2
  53. package/dist/web/RaftStruct.js +97 -26
  54. package/dist/web/RaftStruct.js.map +1 -1
  55. package/dist/web/RaftSystemUtils.d.ts +17 -1
  56. package/dist/web/RaftSystemUtils.js +51 -0
  57. package/dist/web/RaftSystemUtils.js.map +1 -1
  58. package/dist/web/RaftTypes.d.ts +21 -0
  59. package/dist/web/RaftTypes.js.map +1 -1
  60. package/dist/web/main.d.ts +1 -0
  61. package/dist/web/main.js +1 -0
  62. package/dist/web/main.js.map +1 -1
  63. package/examples/dashboard/package.json +1 -1
  64. package/examples/dashboard/src/DeviceActionsForm.tsx +2 -2
  65. package/examples/dashboard/src/DeviceLineChart.tsx +16 -3
  66. package/examples/dashboard/src/DevicePanel.tsx +79 -3
  67. package/examples/dashboard/src/DeviceStatsPanel.tsx +76 -0
  68. package/examples/dashboard/src/DevicesPanel.tsx +11 -0
  69. package/examples/dashboard/src/SettingsScreen.tsx +9 -4
  70. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +10 -2
  71. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +37 -3
  72. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +10 -2
  73. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +38 -4
  74. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +10 -2
  75. package/examples/dashboard/src/styles.css +162 -0
  76. package/package.json +52 -49
  77. package/src/RaftAttributeHandler.ts +25 -22
  78. package/src/RaftChannelSimulated.ts +4 -3
  79. package/src/RaftConnector.ts +34 -13
  80. package/src/RaftDeviceManager.ts +253 -83
  81. package/src/RaftDeviceMgrIF.ts +5 -1
  82. package/src/RaftDeviceStates.ts +42 -8
  83. package/src/RaftPublish.ts +92 -0
  84. package/src/RaftStreamHandler.ts +84 -1
  85. package/src/RaftStruct.test.ts +229 -0
  86. package/src/RaftStruct.ts +101 -37
  87. package/src/RaftSystemUtils.ts +59 -0
  88. package/src/RaftTypes.ts +27 -0
  89. package/src/main.ts +1 -0
@@ -1,5 +1,5 @@
1
1
  import { RaftSubscribeForUpdatesCBType, RaftSystemType } from "../../../../src/RaftSystemType";
2
- import { RaftEventFn, RaftLog, RaftOKFail, RaftPublishEvent, RaftPublishEventNames, RaftSystemUtils } from "../../../../src/main";
2
+ import { inspectPublishFrame, RaftEventFn, RaftLog, RaftPublishEvent, RaftPublishEventNames, RaftSubscriptionUpdateResponse, RaftSystemUtils } from "../../../../src/main";
3
3
  import { CogStateInfo } from "./CogStateInfo";
4
4
  import { DeviceManager } from "../../../../src/RaftDeviceManager";
5
5
 
@@ -53,10 +53,16 @@ export default class SystemTypeCog implements RaftSystemType {
53
53
  ']}';
54
54
 
55
55
  const msgHandler = systemUtils.getMsgHandler();
56
- const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftOKFail>(
56
+ const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftSubscriptionUpdateResponse>(
57
57
  enable ? subscribeEnable : subscribeDisable
58
58
  );
59
59
 
60
+ // Cache topic index->name map from response, then refresh from pubtopics endpoint when enabling
61
+ systemUtils.updatePublishTopicMapFromSubscriptionResponse(ricResp);
62
+ if (enable) {
63
+ await systemUtils.refreshPublishTopicMap();
64
+ }
65
+
60
66
  // Debug
61
67
  RaftLog.debug(`subscribe enable/disable returned ${JSON.stringify(ricResp)}`);
62
68
  } catch (error: unknown) {
@@ -72,13 +78,41 @@ export default class SystemTypeCog implements RaftSystemType {
72
78
 
73
79
  // RICLog.debug(`rxOtherMsgType payload ${RaftUtils.bufferToHex(payload)}`);
74
80
  RaftLog.verbose(`rxOtherMsgType payloadLen ${payload.length}`);
75
- const topicIDs = this._stateInfo.updateFromMsg(payload, frameTimeMs, SUBSCRIBE_BINARY_MSGS);
81
+
82
+ const frameMeta = inspectPublishFrame(payload, (idx) => this._systemUtils?.getPublishTopicName(idx));
83
+ let handledByDeviceManager = false;
84
+
85
+ if (frameMeta.frameType === "binary") {
86
+ if (frameMeta.binaryHasEnvelope) {
87
+ if (frameMeta.topicName === "devbin") {
88
+ this._stateInfo.handleBinaryPayload(payload);
89
+ handledByDeviceManager = true;
90
+ }
91
+ } else if (SUBSCRIBE_BINARY_MSGS) {
92
+ this._stateInfo.handleBinaryPayload(payload);
93
+ handledByDeviceManager = true;
94
+ }
95
+ } else if (frameMeta.frameType === "json") {
96
+ if (frameMeta.topicName === "devjson" || frameMeta.topicName === undefined) {
97
+ if (frameMeta.jsonString !== undefined) {
98
+ this._stateInfo.handleJsonPayload(frameMeta.jsonString);
99
+ handledByDeviceManager = true;
100
+ }
101
+ }
102
+ }
103
+
104
+ const topicIDs = frameMeta.topicIndex !== undefined ? [frameMeta.topicIndex.toString()] : [];
76
105
 
77
106
  // Call event handler if registered
78
107
  if (this._onEvent) {
79
108
  this._onEvent("pub", RaftPublishEvent.PUBLISH_EVENT_DATA, RaftPublishEventNames[RaftPublishEvent.PUBLISH_EVENT_DATA],
80
109
  {
81
110
  topicIDs: topicIDs,
111
+ topicName: frameMeta.topicName,
112
+ topicIndex: frameMeta.topicIndex,
113
+ topicVersion: frameMeta.version,
114
+ frameType: frameMeta.frameType,
115
+ handledByDeviceManager,
82
116
  payload: payload,
83
117
  frameTimeMs: frameTimeMs,
84
118
  isBinary: SUBSCRIBE_BINARY_MSGS
@@ -7,6 +7,14 @@ export class StateInfoGeneric {
7
7
  public constructor(private _deviceManager: DeviceManager) {
8
8
  }
9
9
 
10
+ async handleBinaryPayload(rxMsg: Uint8Array): Promise<void> {
11
+ await this._deviceManager.handleClientMsgBinary(rxMsg);
12
+ }
13
+
14
+ async handleJsonPayload(jsonString: string): Promise<void> {
15
+ await this._deviceManager.handleClientMsgJson(jsonString);
16
+ }
17
+
10
18
  updateFromMsg(rxMsg: Uint8Array, frameTimeMs: number, isBinary: boolean): Array<string> {
11
19
 
12
20
  // Debug
@@ -15,14 +23,14 @@ export class StateInfoGeneric {
15
23
  // Handle binary or JSON
16
24
  if (isBinary) {
17
25
  // Handle using device manager
18
- this._deviceManager.handleClientMsgBinary(rxMsg);
26
+ this.handleBinaryPayload(rxMsg);
19
27
  } else {
20
28
  // Convert Uint8Array to string
21
29
  const decoder = new TextDecoder('utf-8');
22
30
  const jsonString = decoder.decode(rxMsg.slice(2));
23
31
 
24
32
  // Handle using device manager
25
- this._deviceManager.handleClientMsgJson(jsonString);
33
+ this.handleJsonPayload(jsonString);
26
34
  }
27
35
  return [];
28
36
 
@@ -1,9 +1,9 @@
1
1
  import { RaftSubscribeForUpdatesCBType, RaftSystemType } from "../../../../src/RaftSystemType";
2
- import { RaftEventFn, RaftLog, RaftOKFail, RaftPublishEvent, RaftPublishEventNames, RaftSystemUtils } from "../../../../src/main";
2
+ import { inspectPublishFrame, RaftEventFn, RaftLog, RaftPublishEvent, RaftPublishEventNames, RaftSubscriptionUpdateResponse, RaftSystemUtils } from "../../../../src/main";
3
3
  import { StateInfoGeneric } from "./StateInfoGeneric";
4
4
  import { DeviceManager } from "../../../../src/RaftDeviceManager";
5
5
 
6
- const SUBSCRIBE_BINARY_MSGS = false;
6
+ const SUBSCRIBE_BINARY_MSGS = true;
7
7
 
8
8
  export default class SystemTypeGeneric implements RaftSystemType {
9
9
  nameForDialogs = "Generic System";
@@ -53,10 +53,16 @@ export default class SystemTypeGeneric implements RaftSystemType {
53
53
  ']}';
54
54
 
55
55
  const msgHandler = systemUtils.getMsgHandler();
56
- const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftOKFail>(
56
+ const ricResp = await msgHandler.sendRICRESTCmdFrame<RaftSubscriptionUpdateResponse>(
57
57
  enable ? subscribeEnable : subscribeDisable
58
58
  );
59
59
 
60
+ // Cache topic index->name map from response, then refresh from pubtopics endpoint when enabling
61
+ systemUtils.updatePublishTopicMapFromSubscriptionResponse(ricResp);
62
+ if (enable) {
63
+ await systemUtils.refreshPublishTopicMap();
64
+ }
65
+
60
66
  // Debug
61
67
  RaftLog.debug(`subscribe enable/disable returned ${JSON.stringify(ricResp)}`);
62
68
  } catch (error: unknown) {
@@ -72,13 +78,41 @@ export default class SystemTypeGeneric implements RaftSystemType {
72
78
 
73
79
  // RICLog.debug(`rxOtherMsgType payload ${RaftUtils.bufferToHex(payload)}`);
74
80
  RaftLog.verbose(`rxOtherMsgType payloadLen ${payload.length}`);
75
- const topicIDs = this._stateInfo.updateFromMsg(payload, frameTimeMs, SUBSCRIBE_BINARY_MSGS);
81
+
82
+ const frameMeta = inspectPublishFrame(payload, (idx) => this._systemUtils?.getPublishTopicName(idx));
83
+ let handledByDeviceManager = false;
84
+
85
+ if (frameMeta.frameType === "binary") {
86
+ if (frameMeta.binaryHasEnvelope) {
87
+ if (frameMeta.topicName === "devbin") {
88
+ this._stateInfo.handleBinaryPayload(payload);
89
+ handledByDeviceManager = true;
90
+ }
91
+ } else if (SUBSCRIBE_BINARY_MSGS) {
92
+ this._stateInfo.handleBinaryPayload(payload);
93
+ handledByDeviceManager = true;
94
+ }
95
+ } else if (frameMeta.frameType === "json") {
96
+ if (frameMeta.topicName === "devjson" || frameMeta.topicName === undefined) {
97
+ if (frameMeta.jsonString !== undefined) {
98
+ this._stateInfo.handleJsonPayload(frameMeta.jsonString);
99
+ handledByDeviceManager = true;
100
+ }
101
+ }
102
+ }
103
+
104
+ const topicIDs = frameMeta.topicIndex !== undefined ? [frameMeta.topicIndex.toString()] : [];
76
105
 
77
106
  // Call event handler if registered
78
107
  if (this._onEvent) {
79
108
  this._onEvent("pub", RaftPublishEvent.PUBLISH_EVENT_DATA, RaftPublishEventNames[RaftPublishEvent.PUBLISH_EVENT_DATA],
80
109
  {
81
110
  topicIDs: topicIDs,
111
+ topicName: frameMeta.topicName,
112
+ topicIndex: frameMeta.topicIndex,
113
+ topicVersion: frameMeta.version,
114
+ frameType: frameMeta.frameType,
115
+ handledByDeviceManager,
82
116
  payload: payload,
83
117
  frameTimeMs: frameTimeMs,
84
118
  isBinary: SUBSCRIBE_BINARY_MSGS
@@ -1,5 +1,5 @@
1
1
  import RaftDeviceMgrIF from "../../../../src/RaftDeviceMgrIF";
2
- import { DeviceAttributeState, DevicesState, DeviceState } from "../../../../src/RaftDeviceStates";
2
+ import { DeviceAttributeState, DevicesState, DeviceState, DeviceOnlineState } from '../../../../src/RaftDeviceStates';
3
3
  import { RICSERIAL_PAYLOAD_POS } from "../../../../src/RaftProtocolDefs";
4
4
  import RICAddOnManager from "./RICAddOnManager";
5
5
  import RICCommsStats from "./RICCommsStats";
@@ -50,7 +50,7 @@ export class RICStateInfo implements RaftDeviceMgrIF {
50
50
  deviceAttributes: {},
51
51
  deviceIsNew: false,
52
52
  stateChanged: false,
53
- isOnline: false,
53
+ onlineState: DeviceOnlineState.Offline,
54
54
  deviceAddress: "",
55
55
  deviceType: "",
56
56
  busName: ""
@@ -85,6 +85,14 @@ export class RICStateInfo implements RaftDeviceMgrIF {
85
85
  // TODO - implement if RICStateInfo is to be used as a DeviceMgr
86
86
  }
87
87
 
88
+ addDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
89
+ // TODO - implement if RICStateInfo is to be used as a DeviceMgr
90
+ }
91
+
92
+ removeDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
93
+ // TODO - implement if RICStateInfo is to be used as a DeviceMgr
94
+ }
95
+
88
96
  sendAction(deviceKey: string, action: any, data: any): void {
89
97
  // TODO - implement if RICStateInfo is to be used as a DeviceMgr
90
98
  }
@@ -381,6 +381,168 @@ h1 {
381
381
  background-color: #555;
382
382
  }
383
383
 
384
+ .menu-item-toggle {
385
+ padding: 6px 10px;
386
+ }
387
+
388
+ .menu-toggle {
389
+ display: flex;
390
+ align-items: center;
391
+ gap: 8px;
392
+ cursor: pointer;
393
+ }
394
+
395
+ .menu-toggle input {
396
+ margin: 0;
397
+ }
398
+
399
+ .device-stats-panel {
400
+ flex: 0 0 220px;
401
+ min-width: 220px;
402
+ padding: 10px;
403
+ border: 1px solid #666;
404
+ border-radius: 4px;
405
+ background: #444;
406
+ display: flex;
407
+ flex-direction: column;
408
+ gap: 10px;
409
+ }
410
+
411
+ .device-stats-header {
412
+ display: flex;
413
+ justify-content: space-between;
414
+ align-items: center;
415
+ font-weight: bold;
416
+ }
417
+
418
+ .device-stats-reset {
419
+ background: #555;
420
+ color: #fff;
421
+ border: none;
422
+ border-radius: 4px;
423
+ padding: 4px 8px;
424
+ cursor: pointer;
425
+ font-size: 0.8em;
426
+ }
427
+
428
+ .device-stats-reset:hover {
429
+ background: #666;
430
+ }
431
+
432
+ .device-stats-grid {
433
+ display: grid;
434
+ grid-template-columns: 1fr;
435
+ gap: 8px;
436
+ }
437
+
438
+ .device-stats-item {
439
+ display: flex;
440
+ justify-content: space-between;
441
+ gap: 10px;
442
+ }
443
+
444
+ .device-stats-label {
445
+ color: #bbb;
446
+ font-size: 0.85em;
447
+ }
448
+
449
+ .device-stats-value {
450
+ font-weight: 600;
451
+ font-size: 0.9em;
452
+ }
453
+
454
+ .poll-rate-dialog-overlay {
455
+ position: fixed;
456
+ inset: 0;
457
+ background: rgba(0, 0, 0, 0.5);
458
+ z-index: 100;
459
+ display: flex;
460
+ align-items: center;
461
+ justify-content: center;
462
+ }
463
+
464
+ .poll-rate-dialog {
465
+ background: #333;
466
+ border: 1px solid #666;
467
+ border-radius: 6px;
468
+ padding: 18px 22px;
469
+ min-width: 240px;
470
+ display: flex;
471
+ flex-direction: column;
472
+ gap: 10px;
473
+ color: #fff;
474
+ }
475
+
476
+ .poll-rate-dialog-title {
477
+ font-size: 1em;
478
+ font-weight: bold;
479
+ margin-bottom: 4px;
480
+ }
481
+
482
+ .poll-rate-dialog-row {
483
+ display: flex;
484
+ align-items: center;
485
+ gap: 8px;
486
+ }
487
+
488
+ .poll-rate-input {
489
+ flex: 1;
490
+ background: #222;
491
+ border: 1px solid #666;
492
+ border-radius: 4px;
493
+ color: #fff;
494
+ padding: 6px 8px;
495
+ font-size: 1em;
496
+ width: 100%;
497
+ }
498
+
499
+ .poll-rate-unit {
500
+ color: #aaa;
501
+ font-size: 0.9em;
502
+ }
503
+
504
+ .poll-rate-preview {
505
+ font-size: 0.8em;
506
+ color: #aaa;
507
+ }
508
+
509
+ .poll-rate-status {
510
+ font-size: 0.85em;
511
+ color: #f90;
512
+ }
513
+
514
+ .poll-rate-dialog-buttons {
515
+ display: flex;
516
+ gap: 8px;
517
+ justify-content: flex-end;
518
+ }
519
+
520
+ .poll-rate-btn {
521
+ padding: 6px 14px;
522
+ border: none;
523
+ border-radius: 4px;
524
+ cursor: pointer;
525
+ font-size: 0.9em;
526
+ }
527
+
528
+ .poll-rate-btn-set {
529
+ background: #4a7;
530
+ color: #fff;
531
+ }
532
+
533
+ .poll-rate-btn-set:hover {
534
+ background: #5b8;
535
+ }
536
+
537
+ .poll-rate-btn-cancel {
538
+ background: #555;
539
+ color: #fff;
540
+ }
541
+
542
+ .poll-rate-btn-cancel:hover {
543
+ background: #666;
544
+ }
545
+
384
546
  /* Adjust height for portrait orientation */
385
547
  @media (orientation: portrait) {
386
548
  .device-line-chart {
package/package.json CHANGED
@@ -1,51 +1,54 @@
1
1
  {
2
- "name": "@robdobsn/raftjs",
3
- "version": "1.10.7",
4
- "description": "Javascript/TS library for Raft library",
5
- "main": "dist/web/main.js",
6
- "types": "dist/web/main.d.ts",
7
- "react-native": "dist/react-native/main.js",
8
- "author": "Rob Dobson <rob@dobson.com>",
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/robdobsn/raftjs.git"
12
- },
13
- "bugs": {
14
- "url": "https://github.com/robdobsn/raftjs/issues"
15
- },
16
- "license": "MIT",
17
- "keywords": [
18
- "Raft"
19
- ],
20
- "publishConfig": {
21
- "registry": "https://registry.npmjs.org/",
22
- "access": "public"
23
- },
24
- "scripts": {
25
- "build": "tsc -p tsconfig.json",
26
- "build:phone": "tsc -p tsconfig.react-native.json",
27
- "build-all": "npm run clean && npm run build && npm run build:phone",
28
- "lint": "eslint ./src",
29
- "clean": "rimraf dist build package",
30
- "docs": "typedoc --entryPoints src/main.ts",
31
- "watch-all": "tsc -p tsconfig.json --watch & tsc -p tsconfig.react-native.json --watch"
32
- },
33
- "devDependencies": {
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
- },
43
- "dependencies": {
44
- "isomorphic-ws": "^5.0.0",
45
- "tslib": "^2.8.1"
46
- },
47
- "peerDependencies": {
48
- "react-native-ble-plx": "*",
49
- "react-native": "*"
50
- }
2
+ "name": "@robdobsn/raftjs",
3
+ "version": "1.11.6",
4
+ "description": "Javascript/TS library for Raft library",
5
+ "main": "dist/web/main.js",
6
+ "types": "dist/web/main.d.ts",
7
+ "react-native": "dist/react-native/main.js",
8
+ "author": "Rob Dobson <rob@dobson.com>",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/robdobsn/raftjs.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/robdobsn/raftjs/issues"
15
+ },
16
+ "license": "MIT",
17
+ "keywords": [
18
+ "Raft"
19
+ ],
20
+ "publishConfig": {
21
+ "registry": "https://registry.npmjs.org/",
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json",
26
+ "build:phone": "tsc -p tsconfig.react-native.json",
27
+ "build-all": "npm run clean && npm run build && npm run build:phone",
28
+ "lint": "eslint ./src",
29
+ "clean": "rimraf dist build package",
30
+ "docs": "typedoc --entryPoints src/main.ts",
31
+ "watch-all": "tsc -p tsconfig.json --watch & tsc -p tsconfig.react-native.json --watch"
32
+ },
33
+ "devDependencies": {
34
+ "@types/jest": "^30.0.0",
35
+ "@types/node": "^22.13.11",
36
+ "@types/text-encoding": "^0.0.40",
37
+ "@types/web-bluetooth": "^0.0.21",
38
+ "@typescript-eslint/eslint-plugin": "^8.27.0",
39
+ "eslint": "^9.23.0",
40
+ "jest": "^30.3.0",
41
+ "react-native-ble-plx": "^3.5.0",
42
+ "rimraf": "^6.0.1",
43
+ "ts-jest": "^29.4.6",
44
+ "typescript": "^5.8.2"
45
+ },
46
+ "dependencies": {
47
+ "isomorphic-ws": "^5.0.0",
48
+ "tslib": "^2.8.1"
49
+ },
50
+ "peerDependencies": {
51
+ "react-native": "*",
52
+ "react-native-ble-plx": "*"
53
+ }
51
54
  }
@@ -37,7 +37,7 @@ export default class AttributeHandler {
37
37
  const msgDataStartIdx = msgBufIdx;
38
38
 
39
39
  // New attribute values (in order as they appear in the attributes JSON)
40
- let newAttrValues: number[][] = [];
40
+ let newAttrValues: (number | string)[][] = [];
41
41
  if ("c" in pollRespMetadata) {
42
42
 
43
43
  // Extract attribute values using custom handler
@@ -201,7 +201,7 @@ export default class AttributeHandler {
201
201
  }
202
202
  }
203
203
 
204
- private processMsgAttribute(attrDef: DeviceTypeAttribute, msgBuffer: Uint8Array, msgBufIdx: number, msgDataStartIdx: number): { values: number[], newMsgBufIdx: number} {
204
+ private processMsgAttribute(attrDef: DeviceTypeAttribute, msgBuffer: Uint8Array, msgBufIdx: number, msgDataStartIdx: number): { values: (number | string)[], newMsgBufIdx: number} {
205
205
 
206
206
  // Current field message string index
207
207
  let curFieldBufIdx = msgBufIdx;
@@ -253,11 +253,14 @@ export default class AttributeHandler {
253
253
 
254
254
  // Extract the value using python-struct
255
255
  const unpackValues = structUnpack(maskOnSignedValue ? attrTypesOnly.toUpperCase() : attrTypesOnly, attrBuf);
256
- let attrValues = unpackValues as number[];
256
+ let attrValues = unpackValues as (number | string)[];
257
257
 
258
258
  // Get number of bytes consumed
259
259
  const numBytesConsumed = structSizeOf(attrTypesOnly);
260
260
 
261
+ // Check if any values are strings (from 's' format) — skip numeric transforms for those
262
+ const hasStringValues = attrValues.some(v => typeof v === 'string');
263
+
261
264
  // // Check if sign extendable mask specified on signed value
262
265
  // if (mmSpecifiedOnSignedValue) {
263
266
  // const signBitMask = 1 << (signExtendableMaskSignPos - 1);
@@ -270,57 +273,57 @@ export default class AttributeHandler {
270
273
  // }
271
274
 
272
275
  // Check for XOR mask
273
- if ("x" in attrDef) {
276
+ if (!hasStringValues && "x" in attrDef) {
274
277
  const mask = typeof attrDef.x === "string" ? parseInt(attrDef.x, 16) : attrDef.x as number;
275
- attrValues = attrValues.map((value) => (value >>> 0) ^ mask);
278
+ attrValues = attrValues.map((value) => ((value as number) >>> 0) ^ mask);
276
279
  }
277
280
 
278
281
  // Check for AND mask
279
- if ("m" in attrDef) {
282
+ if (!hasStringValues && "m" in attrDef) {
280
283
  const mask = typeof attrDef.m === "string" ? parseInt(attrDef.m, 16) : attrDef.m as number;
281
- attrValues = attrValues.map((value) => (maskOnSignedValue ? this.signExtend(value, mask) : (value >>> 0) & mask));
284
+ attrValues = attrValues.map((value) => (maskOnSignedValue ? this.signExtend(value as number, mask) : ((value as number) >>> 0) & mask));
282
285
  }
283
286
 
284
287
  // Check for a sign-bit
285
- if ("sb" in attrDef) {
288
+ if (!hasStringValues && "sb" in attrDef) {
286
289
  const signBitPos = attrDef.sb as number;
287
290
  const signBitMask = 1 << signBitPos;
288
291
  if ("ss" in attrDef) {
289
292
  const signBitSubtract = attrDef.ss as number;
290
- attrValues = attrValues.map((value) => (value & signBitMask) ? signBitSubtract - value : value);
293
+ attrValues = attrValues.map((value) => ((value as number) & signBitMask) ? signBitSubtract - (value as number) : value);
291
294
  } else {
292
- attrValues = attrValues.map((value) => (value & signBitMask) ? value - (signBitMask << 1) : value);
295
+ attrValues = attrValues.map((value) => ((value as number) & signBitMask) ? (value as number) - (signBitMask << 1) : value);
293
296
  }
294
297
  }
295
298
 
296
299
  // Check for bit shift required
297
- if ("s" in attrDef && attrDef.s) {
300
+ if (!hasStringValues && "s" in attrDef && attrDef.s) {
298
301
  const bitshift = attrDef.s as number;
299
302
  if (bitshift > 0) {
300
- attrValues = attrValues.map((value) => (value >>> 0) >>> bitshift);
303
+ attrValues = attrValues.map((value) => ((value as number) >>> 0) >>> bitshift);
301
304
  } else if (bitshift < 0) {
302
- attrValues = attrValues.map((value) => (value >>> 0) << -bitshift);
305
+ attrValues = attrValues.map((value) => ((value as number) >>> 0) << -bitshift);
303
306
  }
304
307
  }
305
308
 
306
309
  // Check for divisor
307
- if ("d" in attrDef && attrDef.d) {
310
+ if (!hasStringValues && "d" in attrDef && attrDef.d) {
308
311
  const divisor = attrDef.d as number;
309
- attrValues = attrValues.map((value) => (value) / divisor);
312
+ attrValues = attrValues.map((value) => (value as number) / divisor);
310
313
  }
311
314
 
312
315
  // Check for value to add
313
- if ("a" in attrDef && attrDef.a !== undefined) {
316
+ if (!hasStringValues && "a" in attrDef && attrDef.a !== undefined) {
314
317
  const addValue = attrDef.a as number;
315
- attrValues = attrValues.map((value) => (value) + addValue);
318
+ attrValues = attrValues.map((value) => (value as number) + addValue);
316
319
  }
317
320
 
318
321
  // Apply lookup table if defined
319
- if ("lut" in attrDef && attrDef.lut !== undefined) {
322
+ if (!hasStringValues && "lut" in attrDef && attrDef.lut !== undefined) {
320
323
  attrValues = attrValues.map((value): number => {
321
324
  // Skip NaN values
322
- if (isNaN(value)) {
323
- return value;
325
+ if (isNaN(value as number)) {
326
+ return value as number;
324
327
  }
325
328
 
326
329
  // Search through the lookup table rows for a match
@@ -334,7 +337,7 @@ export default class AttributeHandler {
334
337
  }
335
338
 
336
339
  // Parse the range string
337
- if (this.isValueInRangeString(value, row.r)) {
340
+ if (this.isValueInRangeString(value as number, row.r)) {
338
341
  return row.v;
339
342
  }
340
343
  }
@@ -345,7 +348,7 @@ export default class AttributeHandler {
345
348
  }
346
349
 
347
350
  // Otherwise keep the original value
348
- return value;
351
+ return value as number;
349
352
  });
350
353
  }
351
354
 
@@ -357,9 +357,9 @@ export default class RaftChannelSimulated implements RaftChannel {
357
357
  // Convert the buffer to a byte array
358
358
  const dataBytes = new Uint8Array(dataBuffer);
359
359
 
360
- // Create the JSON message structure
360
+ // Create the JSON message structure (bus key is numeric string to match firmware convention)
361
361
  const message = {
362
- "BUS1": {
362
+ "1": {
363
363
  [deviceName]: {
364
364
  "_t": deviceTypeInfo.type,
365
365
  "_o": 1, // Device is online
@@ -464,7 +464,8 @@ export default class RaftChannelSimulated implements RaftChannel {
464
464
  const hotspotRow = (Math.sin(hotspotPhase) + 1) * (rows - 1) / 2;
465
465
  const hotspotCol = (Math.cos(hotspotPhase) + 1) * (cols - 1) / 2;
466
466
  const hotspotAmplitude = 6;
467
- const sigma = Math.max(rows, cols) / 3 || 1;
467
+ const rawSigma = Math.max(rows, cols) / 3;
468
+ const sigma = Number.isFinite(rawSigma) ? rawSigma : 1;
468
469
 
469
470
  for (let idx = 0; idx < repeatCount; idx++) {
470
471
  const row = Math.floor(idx / cols);