@robotical/raftjs 2.1.3 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/devdocs/decode-overrun-investigation.md +167 -0
- package/devdocs/message-panel-design.md +320 -0
- package/dist/react-native/RaftAttributeHandler.d.ts +10 -1
- package/dist/react-native/RaftAttributeHandler.js +60 -8
- package/dist/react-native/RaftAttributeHandler.js.map +1 -1
- package/dist/react-native/RaftChannelWebSerial.js +23 -1
- package/dist/react-native/RaftChannelWebSerial.js.map +1 -1
- package/dist/react-native/RaftConnector.js +23 -0
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +5 -1
- package/dist/react-native/RaftDeviceManager.js +68 -32
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftAttributeHandler.d.ts +10 -1
- package/dist/web/RaftAttributeHandler.js +60 -8
- package/dist/web/RaftAttributeHandler.js.map +1 -1
- package/dist/web/RaftChannelWebSerial.js +23 -1
- package/dist/web/RaftChannelWebSerial.js.map +1 -1
- package/dist/web/RaftConnector.js +23 -0
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +5 -1
- package/dist/web/RaftDeviceManager.js +68 -32
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/examples/dashboard/src/DeviceActionsForm.tsx +3 -0
- package/examples/dashboard/src/DeviceLineChart.tsx +5 -4
- package/examples/dashboard/src/DevicePanel.tsx +3 -0
- package/package.json +1 -1
- package/src/RaftAttributeHandler.ts +89 -9
- package/src/RaftChannelWebSerial.ts +23 -2
- package/src/RaftConnector.ts +30 -0
- package/src/RaftDeviceInfo.ts +5 -1
- package/src/RaftDeviceManager.ts +78 -33
- package/src/main.ts +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Decode Overrun Error Investigation
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
The raftjs example dashboard is receiving an `AttributeHandler decode overrun` error when processing light sensor data from a Cog device. The firmware is sending 9 bytes of sensor data, but the registered schema expects only 8 bytes.
|
|
6
|
+
|
|
7
|
+
## Error Details
|
|
8
|
+
|
|
9
|
+
**Error Message:**
|
|
10
|
+
```
|
|
11
|
+
AttributeHandler decode overrun (msgBuffer):
|
|
12
|
+
deviceKey=0_0
|
|
13
|
+
deviceType=Cog Light Sensors
|
|
14
|
+
debugMsgIndex=1
|
|
15
|
+
attr.n=amb0
|
|
16
|
+
attr.t=>H
|
|
17
|
+
attrTypeSize=2
|
|
18
|
+
curFieldBufIdx=45
|
|
19
|
+
msgBuffer.length=46
|
|
20
|
+
sampleStartIdx=37
|
|
21
|
+
sampleEndIdx=46
|
|
22
|
+
availableInSample=1
|
|
23
|
+
availableInBuffer=1
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Key Facts:**
|
|
27
|
+
- Message buffer total length: 46 bytes
|
|
28
|
+
- Sample data location: bytes 37-46 (9 bytes)
|
|
29
|
+
- Declared sample size in schema: 8 bytes (`pollRespMetadata.b=8`)
|
|
30
|
+
- **Mismatch: Firmware sends 9 bytes, schema expects 8 bytes**
|
|
31
|
+
|
|
32
|
+
## Message Structure Analysis
|
|
33
|
+
|
|
34
|
+
### Binary Message Format
|
|
35
|
+
The firmware sends a binary message with this structure:
|
|
36
|
+
1. **Timestamp** (2 bytes, big-endian) - handled separately before attribute decoding
|
|
37
|
+
2. **Sample length** (1 byte) - value is 9, meaning 9 bytes of sensor data follow
|
|
38
|
+
3. **Sensor data** (9 bytes) - the actual data being decoded
|
|
39
|
+
- IR sensor 0 (ir0): `5c 60` (0x5c60)
|
|
40
|
+
- IR sensor 1 (ir1): `00 05` (0x0005)
|
|
41
|
+
- IR sensor 2 (ir2): `03 00` (0x0300)
|
|
42
|
+
- Ambient sensor 0 (amb0): `02 57` (0x0257)
|
|
43
|
+
- **Extra byte**: `01` (unknown origin)
|
|
44
|
+
|
|
45
|
+
### Expected vs Actual Schema
|
|
46
|
+
|
|
47
|
+
**Schema Definition (from firmware):**
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"name": "Cog Light Sensors",
|
|
51
|
+
"desc": "Light Sensors",
|
|
52
|
+
"type": "RoboCogLightV1",
|
|
53
|
+
"resp": {
|
|
54
|
+
"b": 8,
|
|
55
|
+
"a": [
|
|
56
|
+
{"n": "ir0", "t": ">H", "u": "", "r": [0, 4095], "d": 1, "f": ".0f"},
|
|
57
|
+
{"n": "ir1", "t": ">H", "u": "", "r": [0, 4095], "d": 1, "f": ".0f"},
|
|
58
|
+
{"n": "ir2", "t": ">H", "u": "", "r": [918, 2259], "d": 1, "f": ".0f"},
|
|
59
|
+
{"n": "amb0", "t": ">H", "u": "L", "r": [0, 4095], "d": 1, "f": ".0f"}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Expected data (based on schema):**
|
|
66
|
+
- ir0: 2 bytes
|
|
67
|
+
- ir1: 2 bytes
|
|
68
|
+
- ir2: 2 bytes
|
|
69
|
+
- amb0: 2 bytes
|
|
70
|
+
- **Total: 8 bytes**
|
|
71
|
+
|
|
72
|
+
**Actual data received:**
|
|
73
|
+
- All 4 fields as expected (8 bytes)
|
|
74
|
+
- **Plus 1 extra byte** = 9 bytes total
|
|
75
|
+
|
|
76
|
+
## Root Cause
|
|
77
|
+
|
|
78
|
+
The firmware's `DeviceLightSensors::formDeviceDataResponse()` function in `components/DeviceLightSensors/DeviceLightSensors.cpp` is generating sensor data, and there is a mismatch between:
|
|
79
|
+
|
|
80
|
+
1. **What the firmware sends**: 9 bytes of sensor data (timestamp + data in the binary message)
|
|
81
|
+
2. **What the schema declares**: 8 bytes of sensor data (`"b": 8`)
|
|
82
|
+
|
|
83
|
+
The schema is generated dynamically by the firmware in `getDeviceTypeRecord()` based on the number of configured sensors:
|
|
84
|
+
- 3 IR sensors × 2 bytes = 6 bytes
|
|
85
|
+
- 1 ambient sensor × 2 bytes = 2 bytes
|
|
86
|
+
- **Total: 8 bytes** (correct calculation)
|
|
87
|
+
|
|
88
|
+
But the actual `formDeviceDataResponse()` is sending 9 bytes.
|
|
89
|
+
|
|
90
|
+
## Investigation Points
|
|
91
|
+
|
|
92
|
+
### 1. Extra Byte Origin
|
|
93
|
+
The mysterious `01` byte at the end needs to be identified:
|
|
94
|
+
- Is it padding?
|
|
95
|
+
- Is it a sample count or indicator?
|
|
96
|
+
- Is it an uninitialized buffer value?
|
|
97
|
+
- Was it added in a recent firmware change?
|
|
98
|
+
|
|
99
|
+
**Check:**
|
|
100
|
+
- `git log -p components/DeviceLightSensors/DeviceLightSensors.cpp` around `formDeviceDataResponse()`
|
|
101
|
+
- Look for recent changes that add bytes (new sensor data, flags, etc.)
|
|
102
|
+
- Compare the byte count calculation in `getDeviceTypeRecord()` with actual `formDeviceDataResponse()` logic
|
|
103
|
+
|
|
104
|
+
### 2. Binary Message Encoding
|
|
105
|
+
The binary message encoding happens in `RaftDevice::genBinaryDataMsg()` (in RaftCore). Verify:
|
|
106
|
+
- Is the framework adding extra metadata or padding?
|
|
107
|
+
- Are sample boundaries being correctly calculated?
|
|
108
|
+
- Is there a mismatch between the sample length byte and actual data written?
|
|
109
|
+
|
|
110
|
+
**Check:**
|
|
111
|
+
- `RaftDevice::genBinaryDataMsg()` implementation
|
|
112
|
+
- Recent changes to binary message encoding
|
|
113
|
+
- Sample length byte calculation
|
|
114
|
+
|
|
115
|
+
### 3. Configuration/Build State
|
|
116
|
+
- Check if the firmware was built with debug flags that add extra bytes
|
|
117
|
+
- Verify that sensor configuration hasn't changed (more sensors added?)
|
|
118
|
+
- Check if conditional compilation (#ifdef) is affecting byte counts
|
|
119
|
+
|
|
120
|
+
## raftjs Side
|
|
121
|
+
|
|
122
|
+
The raftjs library correctly detects the mismatch through the diagnostic context added in recent commits:
|
|
123
|
+
- `AttrDecodeDiagContext` interface tracks sample boundaries
|
|
124
|
+
- `RaftAttributeHandler.processMsgAttribute()` bounds-checks against these boundaries
|
|
125
|
+
- `RaftDeviceManager` passes diagnostic context with `sampleStartIdx` and `sampleEndIdx`
|
|
126
|
+
|
|
127
|
+
This is working as intended—the error indicates a real firmware/schema mismatch, not a bug in raftjs.
|
|
128
|
+
|
|
129
|
+
## Recommended Fixes
|
|
130
|
+
|
|
131
|
+
### Short Term (Workaround)
|
|
132
|
+
Update the schema to match reality:
|
|
133
|
+
```json
|
|
134
|
+
"resp": {
|
|
135
|
+
"b": 9, // Changed from 8 to 9
|
|
136
|
+
...
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
This will suppress the error but doesn't fix the root cause.
|
|
140
|
+
|
|
141
|
+
### Long Term (Proper Fix)
|
|
142
|
+
1. **Identify the source** of the extra byte in `formDeviceDataResponse()`
|
|
143
|
+
2. **Either:**
|
|
144
|
+
- Remove the extra byte if it's unintended
|
|
145
|
+
- Properly account for it in the schema and sensor processing if it's intentional
|
|
146
|
+
3. **Update documentation** if there's a reason for the extra byte
|
|
147
|
+
4. **Add unit tests** to prevent this mismatch in future changes
|
|
148
|
+
|
|
149
|
+
## Related Code Locations
|
|
150
|
+
|
|
151
|
+
**Firmware:**
|
|
152
|
+
- Schema definition: `RoboticalCogFW/components/DeviceLightSensors/DeviceLightSensors.cpp` - `getDeviceTypeRecord()`
|
|
153
|
+
- Data encoding: `RoboticalCogFW/components/DeviceLightSensors/DeviceLightSensors.cpp` - `formDeviceDataResponse()`
|
|
154
|
+
- Binary message: `RoboticalCogFW/raftdevlibs/RaftCore/components/core/RaftDevice/RaftDevice.cpp` - `genBinaryDataMsg()`
|
|
155
|
+
|
|
156
|
+
**raftjs:**
|
|
157
|
+
- Attribute handler: `raftjs/src/RaftAttributeHandler.ts` - `processMsgAttribute()`
|
|
158
|
+
- Device manager: `raftjs/src/RaftDeviceManager.ts` - `handleClientMsgBinary()`
|
|
159
|
+
- Diagnostic logging: Added in recent commit with `AttrDecodeDiagContext`
|
|
160
|
+
|
|
161
|
+
## Next Steps
|
|
162
|
+
|
|
163
|
+
1. Run `git log -p` on DeviceLightSensors to find when the extra byte was introduced
|
|
164
|
+
2. Check the firmware build/compile to ensure no debug additions
|
|
165
|
+
3. Examine `formDeviceDataResponse()` line-by-line against sensor configuration
|
|
166
|
+
4. Test with a debugger to verify what bytes are actually being transmitted
|
|
167
|
+
5. Once identified, decide whether to remove the byte or update the schema
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Dashboard Message Panel — Cog-to-Cog IR Comms
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Add a **Message Panel** to the RaftJS example dashboard so a developer can exercise the
|
|
6
|
+
cog-to-cog IR communications feature described in the Cog firmware design document
|
|
7
|
+
(`RoboticalCogFW/devdocs/cog-to-cog-ir-comms-design.md`).
|
|
8
|
+
|
|
9
|
+
The panel sits alongside the existing Command Panel and provides:
|
|
10
|
+
|
|
11
|
+
- A text box to enter an ASCII message.
|
|
12
|
+
- A drop-down list of destinations to send the message to. Initially the only entry is the
|
|
13
|
+
IR channel of a Cog (side 2, the validated comms axis).
|
|
14
|
+
- A button to send the message.
|
|
15
|
+
- A scrolling log of messages received over the same IR channel by the connected device.
|
|
16
|
+
|
|
17
|
+
**No code has been changed yet.** This document records the investigation and describes the
|
|
18
|
+
work required to implement the panel.
|
|
19
|
+
|
|
20
|
+
## Background
|
|
21
|
+
|
|
22
|
+
### How the firmware exposes IR comms
|
|
23
|
+
|
|
24
|
+
The Cog firmware feature is implemented by the `CogIRComms` SysMod and exposed through the
|
|
25
|
+
standard RaftCore REST endpoint mechanism, so the same endpoints work over BLE RICREST,
|
|
26
|
+
WebSocket, and serial. The relevant endpoints (from the firmware design doc, "Useful REST
|
|
27
|
+
commands" and the 2026-05-15 validation section) are:
|
|
28
|
+
|
|
29
|
+
| Endpoint | Purpose |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `ircomms/status` | Returns state plus rx/tx counters and config. Use it to confirm the feature is built/enabled. |
|
|
32
|
+
| `ircomms/tx?side=2&type=<0-15>&seq=<0-15>&payload=<0-255>` | Send a tiny diagnostic packet (single payload byte). |
|
|
33
|
+
| `ircomms/send?side=2&type=<0-15>&seq=<0-15>&hex=<even-length hex, up to 160 chars>` | Send a framed datagram with an arbitrary payload (up to 80 bytes). **This is the endpoint the Message Panel should use.** |
|
|
34
|
+
| `ircomms/rx?pop=1` | Pop the oldest received tiny packet. |
|
|
35
|
+
| `ircomms/rx?popFrame=1` | Pop the oldest received framed datagram. |
|
|
36
|
+
| `ircomms/rx?clear=1` | Clear the receive queue. |
|
|
37
|
+
| `ircomms/cfg?frameMarkUs=<us>&frameSpaceUs=<us>` | Runtime frame-timing tweak (not needed for v1). |
|
|
38
|
+
|
|
39
|
+
The framed payload is the right fit for an ASCII message: `frameMaxPayloadBytes` is 80, i.e.
|
|
40
|
+
160 hex characters, validated on hardware up to 48 bytes with overhead headroom.
|
|
41
|
+
|
|
42
|
+
Side 2 (`IRTX2`/`IRRX2`, mux channel 2) is the only validated bidirectional axis; side 1 did
|
|
43
|
+
not couple in the tested orientations. The destination drop-down should therefore default to
|
|
44
|
+
side 2.
|
|
45
|
+
|
|
46
|
+
### How the firmware notifies of received messages
|
|
47
|
+
|
|
48
|
+
When `rxReportEnable` is true (it is in the default `Cog1` systype config), each CRC-valid
|
|
49
|
+
received message also produces a **RICREST report frame** sent through `SysManager`. For the
|
|
50
|
+
tiny-packet diagnostic the report looks like:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{"msgType":"ircomms","msgName":"rx","rxMs":11134,"type":7,"seq":8,"payload":55,"crc":55883}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The firmware design doc's "Receive Notification Recommendation" section proposes a richer
|
|
57
|
+
report for framed messages with `source`, `type`, and a `payload` field. The exact shape of
|
|
58
|
+
the report emitted for **framed** `ircomms/send` datagrams is not pinned down in the firmware
|
|
59
|
+
doc — see [Open questions](#open-questions-firmware-coordination).
|
|
60
|
+
|
|
61
|
+
### How RaftJS sends and receives
|
|
62
|
+
|
|
63
|
+
The dashboard talks to one device at a time through a single `RaftConnector` instance
|
|
64
|
+
(`ConnManager.getInstance().getConnector()`).
|
|
65
|
+
|
|
66
|
+
**Sending** — `CommandPanel.tsx` already shows the pattern:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
connManager.getConnector().sendRICRESTMsg(commandName, params)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`sendRICRESTMsg(commandName, params)` (in `src/RaftConnector.ts:412`) builds a query string
|
|
73
|
+
from `params`, URL-encodes each value, appends it to `commandName`, sends it as a RICREST URL
|
|
74
|
+
message, and resolves to the parsed JSON response (typed `RaftOKFail`, but the full JSON of
|
|
75
|
+
the device response is returned). So a framed send is simply:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const resp = await connManager.getConnector().sendRICRESTMsg('ircomms/send', {
|
|
79
|
+
side: 2, type: 1, seq: seqNo, hex: asciiToHex(messageText),
|
|
80
|
+
});
|
|
81
|
+
// resp e.g. { rslt: "ok", seq: 42, queued: 1 }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Polling the receive queue uses the same call:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const resp = await connManager.getConnector().sendRICRESTMsg('ircomms/rx', { popFrame: 1 });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Receiving reports** — `RaftMsgHandler` decodes `MSG_TYPE_REPORT` frames, parses the JSON,
|
|
91
|
+
and dispatches it to every callback registered via `reportMsgCallbacksSet`
|
|
92
|
+
(`src/RaftMsgHandler.ts:144`). The connector exposes the handler publicly:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const handler = connManager.getConnector().getRaftMsgHandler(); // RaftConnector.ts:220
|
|
96
|
+
handler.reportMsgCallbacksSet('messagePanel', async (report) => { ... });
|
|
97
|
+
// on unmount:
|
|
98
|
+
handler.reportMsgCallbacksDelete('messagePanel');
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The callback receives a `RaftReportMsg` object (exported from `src/main.ts` via
|
|
102
|
+
`export * from './RaftTypes'`). Note `RaftReportMsg` only declares a fixed set of fields;
|
|
103
|
+
the `ircomms` report carries extra fields (`type`, `seq`, `payload`, `crc`, `rxMs`, …) that
|
|
104
|
+
must be read by extending/casting the type.
|
|
105
|
+
|
|
106
|
+
The connector already registers an internal `"eventHandler"` report callback for `sysevent`
|
|
107
|
+
shutdown handling — adding a second `"messagePanel"` callback is independent and supported.
|
|
108
|
+
|
|
109
|
+
## Proposed design
|
|
110
|
+
|
|
111
|
+
### New component: `MessagePanel.tsx`
|
|
112
|
+
|
|
113
|
+
Add `examples/dashboard/src/MessagePanel.tsx`, structured like `CommandPanel.tsx`:
|
|
114
|
+
|
|
115
|
+
- A two-column `info-box` (reuse the existing `info-boxes` / `info-box` / `info-columns`
|
|
116
|
+
layout classes).
|
|
117
|
+
- **Left column** — message composer: ASCII text input, destination `<select>`, Send button.
|
|
118
|
+
- **Right column** — received-message log: a scrollable list, newest last (or newest first),
|
|
119
|
+
with a Clear button.
|
|
120
|
+
|
|
121
|
+
Suggested layout:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
+-------------------------- Message Panel ---------------------------+
|
|
125
|
+
| Compose | Received (IR side 2) |
|
|
126
|
+
| [ Enter ASCII message_________ ] | 12:01:03 src? "hello" |
|
|
127
|
+
| Destination: [ Cog IR side 2 v ] | 12:01:09 src? "ping back" |
|
|
128
|
+
| [ Send Message ] | ... |
|
|
129
|
+
| status: queued seq 42 | [ Clear log ] |
|
|
130
|
+
+--------------------------------------------------------------------+
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Destination drop-down (data-driven)
|
|
134
|
+
|
|
135
|
+
Make the destination list a small array so future destinations are easy to add:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
interface IRDestination {
|
|
139
|
+
label: string; // shown in the drop-down
|
|
140
|
+
endpoint: string; // e.g. 'ircomms/send'
|
|
141
|
+
baseParams: object; // e.g. { side: 2, type: 1 }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const destinations: IRDestination[] = [
|
|
145
|
+
{ label: 'Cog IR (side 2)', endpoint: 'ircomms/send', baseParams: { side: 2, type: 1 } },
|
|
146
|
+
];
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The `seq` and `hex` parameters are added per-send. Future entries could target side 1, the
|
|
150
|
+
tiny-packet `ircomms/tx` endpoint, or other transports without changing the panel logic.
|
|
151
|
+
|
|
152
|
+
### Send path
|
|
153
|
+
|
|
154
|
+
1. Read the ASCII text from the input.
|
|
155
|
+
2. Validate: non-empty, and ≤ 80 characters (160 hex chars — the `ircomms/send` limit). Show
|
|
156
|
+
an inline error otherwise.
|
|
157
|
+
3. Convert ASCII → hex (see [helpers](#asciihex-helpers)).
|
|
158
|
+
4. Maintain a rolling `seq` counter. The validated diagnostic commands use `seq` in the
|
|
159
|
+
`0-15` range, so wrap with `seq = (seq + 1) & 0x0f`.
|
|
160
|
+
5. Call `sendRICRESTMsg(dest.endpoint, { ...dest.baseParams, seq, hex })`.
|
|
161
|
+
6. Show the result (`resp.rslt`, `resp.seq`, `resp.queued`) as a small status line, and warn
|
|
162
|
+
on `rslt !== 'ok'`. Keep a local sent-message history (optional, mirrors CommandPanel's
|
|
163
|
+
command history / arrow-key recall).
|
|
164
|
+
|
|
165
|
+
### Receive path
|
|
166
|
+
|
|
167
|
+
The connected device receives IR messages from *another* Cog automatically via its wake-probe
|
|
168
|
+
path and queues them. The dashboard surfaces them in the log. Two mechanisms are available:
|
|
169
|
+
|
|
170
|
+
**A. Polling `ircomms/rx?popFrame=1` (recommended primary mechanism for v1).**
|
|
171
|
+
|
|
172
|
+
- On a timer (e.g. every 750 ms) while connected, call `sendRICRESTMsg('ircomms/rx', { popFrame: 1 })`.
|
|
173
|
+
- If the response contains a frame, decode its payload hex → ASCII and append a log entry,
|
|
174
|
+
then poll again immediately to drain the queue; stop when empty.
|
|
175
|
+
- This reliably returns the full payload regardless of report-frame shape, which is why it is
|
|
176
|
+
preferred for the first implementation.
|
|
177
|
+
|
|
178
|
+
**B. Report subscription (low-latency enhancement).**
|
|
179
|
+
|
|
180
|
+
- Register a `messagePanel` report callback and filter for
|
|
181
|
+
`report.msgType?.toLowerCase() === 'ircomms' && report.msgName?.toLowerCase() === 'rx'`.
|
|
182
|
+
- Use it either to display the message directly (if the report includes the payload) or
|
|
183
|
+
simply to trigger an immediate drain of `ircomms/rx?popFrame=1` instead of waiting for the
|
|
184
|
+
next poll tick.
|
|
185
|
+
|
|
186
|
+
**Recommendation:** implement (A) first for a working, reliable panel. Add (B) as a latency
|
|
187
|
+
improvement once the firmware report shape for framed messages is confirmed. Both can run
|
|
188
|
+
together — the report callback just triggers an early poll.
|
|
189
|
+
|
|
190
|
+
A "Clear log" button should clear the local log; optionally also call `ircomms/rx?clear=1` to
|
|
191
|
+
clear the device-side queue so stale messages are not re-polled.
|
|
192
|
+
|
|
193
|
+
### ASCII/hex helpers
|
|
194
|
+
|
|
195
|
+
`ConnManager` already has `hexStringToBytes()`. Two more small pure functions are needed —
|
|
196
|
+
put them in `MessagePanel.tsx` or a shared `examples/dashboard/src/utils.ts`:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
// 'hi' -> '6869'
|
|
200
|
+
function asciiToHex(text: string): string {
|
|
201
|
+
return Array.from(text, c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// '6869' -> 'hi' (non-printable bytes shown as '.', or escaped)
|
|
205
|
+
function hexToAscii(hex: string): string {
|
|
206
|
+
const out = (hex.match(/.{1,2}/g) ?? []).map(b => parseInt(b, 16));
|
|
207
|
+
return out.map(c => (c >= 0x20 && c < 0x7f) ? String.fromCharCode(c) : '.').join('');
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Restrict the input to the ASCII printable range (0x20–0x7e) on send, or document that
|
|
212
|
+
non-ASCII characters are encoded as their lower byte only.
|
|
213
|
+
|
|
214
|
+
### CSS
|
|
215
|
+
|
|
216
|
+
Reuse `command-input` and `send-command-button` for the input and button. Add a few classes
|
|
217
|
+
to `examples/dashboard/src/styles.css` for the new elements, following the existing dark
|
|
218
|
+
theme (`#333`/`#444` backgrounds, `#666` borders):
|
|
219
|
+
|
|
220
|
+
- `.message-destination-select` — the drop-down.
|
|
221
|
+
- `.message-log` — scrollable container (`max-height`, `overflow-y: auto`).
|
|
222
|
+
- `.message-log-entry` — one received message (timestamp + text).
|
|
223
|
+
- `.message-status` — the send-result status line.
|
|
224
|
+
|
|
225
|
+
### Integration into `Main.tsx`
|
|
226
|
+
|
|
227
|
+
Render `<MessagePanel />` immediately after `<CommandPanel />` inside the
|
|
228
|
+
`connected-panel` block (`examples/dashboard/src/Main.tsx:178`). It only makes sense when
|
|
229
|
+
connected, which that block already guarantees.
|
|
230
|
+
|
|
231
|
+
Optionally gate the panel so it only appears for Cog devices: on mount, call
|
|
232
|
+
`ircomms/status` once and only enable the panel (or show a "not available on this device"
|
|
233
|
+
note) if it returns `rslt: "ok"`. This avoids a confusing dead panel when connected to a
|
|
234
|
+
Marty or generic device. For a first cut the panel can always render and simply report
|
|
235
|
+
failures from `sendRICRESTMsg`.
|
|
236
|
+
|
|
237
|
+
## Open questions / firmware coordination
|
|
238
|
+
|
|
239
|
+
These should be confirmed against the actual firmware build before or during implementation:
|
|
240
|
+
|
|
241
|
+
1. **Framed receive report shape.** The firmware doc shows the tiny-packet report
|
|
242
|
+
(`payload` as a single number). It does not pin down the report emitted for framed
|
|
243
|
+
`ircomms/send` datagrams. For mechanism (B) to display message text directly, the
|
|
244
|
+
`ircomms`/`rx` report should include the payload as `payloadHex` (or an escaped ASCII
|
|
245
|
+
string). If it does not, the panel must rely on polling (mechanism A). **Recommended
|
|
246
|
+
firmware change:** include `payloadHex` and `len` in the framed-receive report.
|
|
247
|
+
2. **`ircomms/rx?popFrame=1` response fields.** Confirm the exact JSON field names returned
|
|
248
|
+
(the firmware doc variously mentions `frameHex`, `payloadHex`, `rxnext`/`rxpeek`,
|
|
249
|
+
`rx?popFrame`). The panel's decode step depends on the actual field name, and on what is
|
|
250
|
+
returned when the queue is empty (`rslt: "ok"` with no frame vs `rslt: "fail"`).
|
|
251
|
+
3. **`text=` parameter.** Some diagnostic examples use `ircomms/send?...&text=hi` directly.
|
|
252
|
+
If `text=` is reliably supported, the dashboard could skip hex conversion. The validated
|
|
253
|
+
command list uses `hex=`, so `hex=` is the safer default.
|
|
254
|
+
4. **Device prerequisites.** The connected Cog must be running a build with `CogIRComms`
|
|
255
|
+
compiled in and `CogIRComms.enable = 1` (and `rxReportEnable = 1` for mechanism B) in its
|
|
256
|
+
systype config. The dashboard cannot set these — they are firmware build/config
|
|
257
|
+
preconditions. Surface `ircomms/status` so the user can see whether the feature is live.
|
|
258
|
+
5. **Two devices needed for a real test.** The dashboard connects to one Cog. Sends leave
|
|
259
|
+
that Cog over IR; the receive log shows what that Cog received over IR from a *second*
|
|
260
|
+
Cog. A meaningful round-trip test needs two Cogs physically aligned on side 2, and
|
|
261
|
+
typically two dashboard instances (one per Cog) — or the second Cog echoing messages.
|
|
262
|
+
6. **`seq` range.** The framed envelope carries a 1-byte sequence number, but the validated
|
|
263
|
+
diagnostic commands constrain `seq` to `0-15`. Wrap the rolling counter at 16 unless
|
|
264
|
+
firmware confirms the full byte range is accepted by `ircomms/send`.
|
|
265
|
+
|
|
266
|
+
## Implementation steps
|
|
267
|
+
|
|
268
|
+
1. Add `asciiToHex` / `hexToAscii` helpers (in `MessagePanel.tsx` or a shared util).
|
|
269
|
+
2. Create `examples/dashboard/src/MessagePanel.tsx`:
|
|
270
|
+
- State: message text, selected destination, rolling `seq`, send-status, received log.
|
|
271
|
+
- Send handler → `sendRICRESTMsg('ircomms/send', {...})`.
|
|
272
|
+
- Receive: poll `ircomms/rx?popFrame=1` on an interval via `useEffect`/`setInterval`
|
|
273
|
+
(clean up on unmount); decode hex payload → ASCII; append to log.
|
|
274
|
+
- Optional: register/deregister a `messagePanel` report callback for low-latency
|
|
275
|
+
notification.
|
|
276
|
+
3. Add the destination array (one entry: Cog IR side 2).
|
|
277
|
+
4. Add CSS classes to `styles.css`.
|
|
278
|
+
5. Wire `<MessagePanel />` into `Main.tsx` after `<CommandPanel />`.
|
|
279
|
+
6. (Optional) Gate the panel on an `ircomms/status` probe.
|
|
280
|
+
|
|
281
|
+
## Related: feature-gating panels on capability probes
|
|
282
|
+
|
|
283
|
+
The Message Panel will probe `ircomms/status` once on connect to decide whether to render
|
|
284
|
+
(see step 6 above). The same pattern should be applied to the existing **datalog** panels
|
|
285
|
+
(`LogConfigPanel`, `LoggingPanel`, `LogFilesPanel`), because some firmwares do not support
|
|
286
|
+
that API and the dashboard currently produces noisy console errors against them:
|
|
287
|
+
|
|
288
|
+
```text
|
|
289
|
+
_handleResponseMessages RICREST rslt fail msgNum 115
|
|
290
|
+
resp {"req":"datalog?action=status","rslt":"fail","error":"failUnknownAPI"}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Proposed work (separate from but parallel to the Message Panel):
|
|
294
|
+
|
|
295
|
+
1. On (re)connect, issue a single `datalog?action=status` (or equivalent capability) call.
|
|
296
|
+
2. Cache the result as a "datalog supported" boolean on `ConnManager` or a small capability
|
|
297
|
+
context, keyed off the current connection.
|
|
298
|
+
3. Conditionally render `LogConfigPanel` / `LoggingPanel` / `LogFilesPanel` and suppress any
|
|
299
|
+
periodic datalog polling when the capability is absent.
|
|
300
|
+
4. Reset the cached capability on disconnect so reconnecting to a different device re-probes.
|
|
301
|
+
|
|
302
|
+
A small shared helper — e.g. `useFeatureSupported(probeApi: string): boolean | undefined` —
|
|
303
|
+
would let both the Message Panel and the datalog panels use the same gating pattern, and
|
|
304
|
+
keep `failUnknownAPI` errors out of the console for any feature that is not built into the
|
|
305
|
+
connected firmware.
|
|
306
|
+
|
|
307
|
+
## Testing
|
|
308
|
+
|
|
309
|
+
- **Single device, no peer:** connect to one Cog, send a message, confirm `sendRICRESTMsg`
|
|
310
|
+
resolves with `rslt: "ok"` and the status line updates. The receive log stays empty.
|
|
311
|
+
- **Two devices:** align two Cogs side-2-to-side-2 (~10 mm gap, per the firmware validation
|
|
312
|
+
setup). Connect a dashboard to each. Send from one and confirm the message appears in the
|
|
313
|
+
other's receive log with the correct decoded ASCII text.
|
|
314
|
+
- **Round-trip:** send in both directions; confirm sequence numbers advance and no messages
|
|
315
|
+
are dropped.
|
|
316
|
+
- **Edge cases:** empty message rejected; over-length message (>80 chars) rejected with a
|
|
317
|
+
clear error; non-printable received bytes rendered safely; panel behaves correctly across
|
|
318
|
+
disconnect/reconnect (timers and report callbacks cleaned up).
|
|
319
|
+
- **Regression:** confirm the existing Command Panel and other panels are unaffected by the
|
|
320
|
+
added report callback and polling.
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { DeviceTypePollRespMetadata } from "./RaftDeviceInfo";
|
|
2
2
|
import { DeviceAttributesState, DeviceTimeline } from "./RaftDeviceStates";
|
|
3
|
+
export interface AttrDecodeDiagContext {
|
|
4
|
+
deviceKey?: string;
|
|
5
|
+
deviceType?: string;
|
|
6
|
+
debugMsgIndex?: number;
|
|
7
|
+
sampleStartIdx?: number;
|
|
8
|
+
sampleEndIdx?: number;
|
|
9
|
+
}
|
|
3
10
|
export default class AttributeHandler {
|
|
4
11
|
private _customAttrHandler;
|
|
5
12
|
private POLL_RESULT_TIMESTAMP_SIZE;
|
|
6
13
|
private POLL_RESULT_WRAP_VALUE;
|
|
7
14
|
private POLL_RESULT_RESOLUTION_US;
|
|
8
|
-
processMsgAttrGroup(msgBuffer: Uint8Array, msgBufIdx: number, deviceTimeline: DeviceTimeline, pollRespMetadata: DeviceTypePollRespMetadata, devAttrsState: DeviceAttributesState, maxDataPoints: number,
|
|
15
|
+
processMsgAttrGroup(msgBuffer: Uint8Array, msgBufIdx: number, deviceTimeline: DeviceTimeline, pollRespMetadata: DeviceTypePollRespMetadata, devAttrsState: DeviceAttributesState, maxDataPoints: number, msgEndIdxOrDiagCtx?: number | AttrDecodeDiagContext, diagCtx?: AttrDecodeDiagContext): number;
|
|
9
16
|
private validateAttributes;
|
|
10
17
|
private processMsgAttribute;
|
|
18
|
+
private _overrunWarnSeen;
|
|
19
|
+
private warnAttrOverrun;
|
|
11
20
|
private signExtend;
|
|
12
21
|
private extractTimestampAndAdvanceIdx;
|
|
13
22
|
private isValueInRangeString;
|
|
@@ -12,6 +12,7 @@ const tslib_1 = require("tslib");
|
|
|
12
12
|
const RaftCustomAttrHandler_1 = tslib_1.__importDefault(require("./RaftCustomAttrHandler"));
|
|
13
13
|
const RaftDeviceInfo_1 = require("./RaftDeviceInfo");
|
|
14
14
|
const RaftStruct_1 = require("./RaftStruct");
|
|
15
|
+
const RaftUtils_1 = tslib_1.__importDefault(require("./RaftUtils"));
|
|
15
16
|
class AttributeHandler {
|
|
16
17
|
constructor() {
|
|
17
18
|
// Custom attribute handler
|
|
@@ -20,8 +21,21 @@ class AttributeHandler {
|
|
|
20
21
|
this.POLL_RESULT_TIMESTAMP_SIZE = 2;
|
|
21
22
|
this.POLL_RESULT_WRAP_VALUE = this.POLL_RESULT_TIMESTAMP_SIZE === 2 ? 65536 : 4294967296;
|
|
22
23
|
this.POLL_RESULT_RESOLUTION_US = 100;
|
|
24
|
+
// One-shot detailed warning when an attribute decode would overrun the
|
|
25
|
+
// sample/message bounds. Includes the exact bytes and schema so the
|
|
26
|
+
// firmware vs. registered device-type schema can be reconciled.
|
|
27
|
+
this._overrunWarnSeen = new Set();
|
|
23
28
|
}
|
|
24
|
-
processMsgAttrGroup(msgBuffer, msgBufIdx, deviceTimeline, pollRespMetadata, devAttrsState, maxDataPoints,
|
|
29
|
+
processMsgAttrGroup(msgBuffer, msgBufIdx, deviceTimeline, pollRespMetadata, devAttrsState, maxDataPoints, msgEndIdxOrDiagCtx = msgBuffer.length, diagCtx) {
|
|
30
|
+
var _a;
|
|
31
|
+
// Merge rationale: Robotical's devbin compatibility parser needs an
|
|
32
|
+
// explicit sample boundary; upstream's diagnostics need per-sample
|
|
33
|
+
// context. Accept both call styles so malformed samples are skipped
|
|
34
|
+
// without losing useful overrun warnings.
|
|
35
|
+
const msgEndIdx = typeof msgEndIdxOrDiagCtx === "number"
|
|
36
|
+
? msgEndIdxOrDiagCtx
|
|
37
|
+
: (_a = msgEndIdxOrDiagCtx.sampleEndIdx) !== null && _a !== void 0 ? _a : msgBuffer.length;
|
|
38
|
+
const effectiveDiagCtx = typeof msgEndIdxOrDiagCtx === "number" ? diagCtx : msgEndIdxOrDiagCtx;
|
|
25
39
|
// console.log(`processMsgAttrGroup msg ${msgHexStr} timestamp ${timestamp} origTimestamp ${origTimestamp} msgBufIdx ${msgBufIdx}`)
|
|
26
40
|
const boundedMsgEndIdx = Math.min(Math.max(msgEndIdx, msgBufIdx), msgBuffer.length);
|
|
27
41
|
// Extract msg timestamp
|
|
@@ -33,6 +47,11 @@ class AttributeHandler {
|
|
|
33
47
|
const msgDataStartIdx = msgBufIdx;
|
|
34
48
|
// New attribute values (in order as they appear in the attributes JSON)
|
|
35
49
|
let newAttrValues = [];
|
|
50
|
+
// Tracks whether any individual attribute decode failed in the non-custom path.
|
|
51
|
+
// When true, the detailed per-attribute overrun warning has already been emitted
|
|
52
|
+
// by processMsgAttribute, so we suppress the redundant downstream length/empty
|
|
53
|
+
// warnings that would otherwise fire every poll.
|
|
54
|
+
let attrDecodeFailed = false;
|
|
36
55
|
if ("c" in pollRespMetadata) {
|
|
37
56
|
// Extract attribute values using custom handler
|
|
38
57
|
newAttrValues = this._customAttrHandler.handleAttr(pollRespMetadata, msgBuffer, msgBufIdx, boundedMsgEndIdx);
|
|
@@ -71,11 +90,12 @@ class AttributeHandler {
|
|
|
71
90
|
if (!("t" in attrDef)) {
|
|
72
91
|
console.warn(`DeviceManager msg unknown msgBuffer ${msgBuffer} tsUs ${timestampUs} attrDef ${JSON.stringify(attrDef)}`);
|
|
73
92
|
newAttrValues.push([]);
|
|
93
|
+
attrDecodeFailed = true;
|
|
74
94
|
continue;
|
|
75
95
|
}
|
|
76
96
|
// console.log(`RaftAttrHdlr.processMsgAttrGroup attr ${attrDef.n} msgBufIdx ${msgBufIdx} timestampUs ${timestampUs} attrDef ${JSON.stringify(attrDef)}`);
|
|
77
97
|
// Process the attribute
|
|
78
|
-
const { values, newMsgBufIdx } = this.processMsgAttribute(attrDef, msgBuffer, msgBufIdx, msgDataStartIdx, boundedMsgEndIdx);
|
|
98
|
+
const { values, newMsgBufIdx } = this.processMsgAttribute(attrDef, msgBuffer, msgBufIdx, msgDataStartIdx, boundedMsgEndIdx, pollRespMetadata, effectiveDiagCtx);
|
|
79
99
|
if (newMsgBufIdx < 0) {
|
|
80
100
|
return -1;
|
|
81
101
|
}
|
|
@@ -97,13 +117,17 @@ class AttributeHandler {
|
|
|
97
117
|
const numNewDataPoints = newAttrValues[0].length;
|
|
98
118
|
for (let i = 1; i < newAttrValues.length; i++) {
|
|
99
119
|
if (newAttrValues[i].length !== numNewDataPoints) {
|
|
100
|
-
|
|
120
|
+
if (!attrDecodeFailed) {
|
|
121
|
+
console.warn(`DeviceManager msg attrGroup ${JSON.stringify(pollRespMetadata)} attrName ${pollRespMetadata.a[i].n} newAttrValues lengths ${newAttrValues.map(v => v.length).join(",")} do not match`);
|
|
122
|
+
}
|
|
101
123
|
return msgDataStartIdx + pollRespSizeBytes;
|
|
102
124
|
}
|
|
103
125
|
}
|
|
104
126
|
// All attributes in the schema should have values
|
|
105
127
|
if (newAttrValues.length !== pollRespMetadata.a.length) {
|
|
106
|
-
|
|
128
|
+
if (!attrDecodeFailed) {
|
|
129
|
+
console.warn(`DeviceManager msg attrGroup ${JSON.stringify(pollRespMetadata)} newAttrValues length ${newAttrValues.length} does not match attrGroup.a length ${pollRespMetadata.a.length}`);
|
|
130
|
+
}
|
|
107
131
|
return msgDataStartIdx + pollRespSizeBytes;
|
|
108
132
|
}
|
|
109
133
|
// Add the new attribute values to the device state
|
|
@@ -238,7 +262,7 @@ class AttributeHandler {
|
|
|
238
262
|
}
|
|
239
263
|
}
|
|
240
264
|
}
|
|
241
|
-
processMsgAttribute(attrDef, msgBuffer, msgBufIdx, msgDataStartIdx, msgEndIdx) {
|
|
265
|
+
processMsgAttribute(attrDef, msgBuffer, msgBufIdx, msgDataStartIdx, msgEndIdx, pollRespMetadata, diagCtx) {
|
|
242
266
|
// Current field message string index
|
|
243
267
|
let curFieldBufIdx = msgBufIdx;
|
|
244
268
|
let attrUsesAbsPos = false;
|
|
@@ -257,7 +281,8 @@ class AttributeHandler {
|
|
|
257
281
|
// Copy bytes from the specified positions
|
|
258
282
|
for (let i = 0; i < attrDef.at.length && i < elemSize; i++) {
|
|
259
283
|
const sourceIdx = msgDataStartIdx + attrDef.at[i];
|
|
260
|
-
if (sourceIdx >= boundedMsgEndIdx) {
|
|
284
|
+
if (sourceIdx < msgDataStartIdx || sourceIdx >= boundedMsgEndIdx) {
|
|
285
|
+
this.warnAttrOverrun(attrDef, msgBuffer, sourceIdx, elemSize, msgDataStartIdx, true, pollRespMetadata, diagCtx, sourceIdx >= msgBuffer.length ? "msgBuffer" : "sample");
|
|
261
286
|
return { values: [], newMsgBufIdx: -1 };
|
|
262
287
|
}
|
|
263
288
|
bytesForType[i] = msgBuffer[sourceIdx];
|
|
@@ -273,11 +298,13 @@ class AttributeHandler {
|
|
|
273
298
|
}
|
|
274
299
|
attrUsesAbsPos = true;
|
|
275
300
|
}
|
|
276
|
-
//
|
|
301
|
+
// Merge rationale: keep Robotical's hard sample bounds as the source of
|
|
302
|
+
// truth, but emit upstream's one-shot diagnostic when a schema tries to
|
|
303
|
+
// read beyond the sample or buffer.
|
|
277
304
|
const attrEndIdx = curFieldBufIdx + numBytesConsumed;
|
|
278
305
|
const effectiveMsgEndIdx = Math.min(Math.max(msgEndIdx, curFieldBufIdx), msgBuffer.length);
|
|
279
306
|
if (curFieldBufIdx >= effectiveMsgEndIdx || attrEndIdx > effectiveMsgEndIdx) {
|
|
280
|
-
|
|
307
|
+
this.warnAttrOverrun(attrDef, msgBuffer, curFieldBufIdx, numBytesConsumed, msgDataStartIdx, attrUsesAbsPos, pollRespMetadata, diagCtx, attrEndIdx > msgBuffer.length ? "msgBuffer" : "sample");
|
|
281
308
|
return { values: [], newMsgBufIdx: -1 };
|
|
282
309
|
}
|
|
283
310
|
// Slice into buffer
|
|
@@ -379,6 +406,31 @@ class AttributeHandler {
|
|
|
379
406
|
// Return the value
|
|
380
407
|
return { values: attrValues, newMsgBufIdx: msgBufIdx };
|
|
381
408
|
}
|
|
409
|
+
warnAttrOverrun(attrDef, msgBuffer, curFieldBufIdx, attrTypeSize, msgDataStartIdx, attrUsesAbsPos, pollRespMetadata, diagCtx, overrunOf) {
|
|
410
|
+
var _a, _b, _c, _d, _e, _f;
|
|
411
|
+
const sampleStart = (_a = diagCtx === null || diagCtx === void 0 ? void 0 : diagCtx.sampleStartIdx) !== null && _a !== void 0 ? _a : msgDataStartIdx;
|
|
412
|
+
const sampleEnd = diagCtx === null || diagCtx === void 0 ? void 0 : diagCtx.sampleEndIdx;
|
|
413
|
+
const dedupeKey = `${(_b = diagCtx === null || diagCtx === void 0 ? void 0 : diagCtx.deviceKey) !== null && _b !== void 0 ? _b : "?"}|${(_c = diagCtx === null || diagCtx === void 0 ? void 0 : diagCtx.deviceType) !== null && _c !== void 0 ? _c : "?"}|${attrDef.n}|${attrDef.t}|${attrUsesAbsPos ? attrDef.at : "rel"}`;
|
|
414
|
+
if (this._overrunWarnSeen.has(dedupeKey)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this._overrunWarnSeen.add(dedupeKey);
|
|
418
|
+
const sampleHex = sampleEnd !== undefined
|
|
419
|
+
? RaftUtils_1.default.bufferToHex(msgBuffer.slice(sampleStart, sampleEnd))
|
|
420
|
+
: "<unknown sample bounds>";
|
|
421
|
+
const availableInSample = sampleEnd !== undefined ? Math.max(0, sampleEnd - curFieldBufIdx) : -1;
|
|
422
|
+
const availableInBuffer = Math.max(0, msgBuffer.length - curFieldBufIdx);
|
|
423
|
+
console.warn(`AttributeHandler decode overrun (${overrunOf}): ` +
|
|
424
|
+
`deviceKey=${(_d = diagCtx === null || diagCtx === void 0 ? void 0 : diagCtx.deviceKey) !== null && _d !== void 0 ? _d : "?"} deviceType=${(_e = diagCtx === null || diagCtx === void 0 ? void 0 : diagCtx.deviceType) !== null && _e !== void 0 ? _e : "?"} ` +
|
|
425
|
+
`debugMsgIndex=${(_f = diagCtx === null || diagCtx === void 0 ? void 0 : diagCtx.debugMsgIndex) !== null && _f !== void 0 ? _f : "?"} attr.n=${attrDef.n} attr.t=${attrDef.t} ` +
|
|
426
|
+
`attrTypeSize=${attrTypeSize} attrUsesAbsPos=${attrUsesAbsPos} attr.at=${JSON.stringify(attrDef.at)} ` +
|
|
427
|
+
`curFieldBufIdx=${curFieldBufIdx} msgBuffer.length=${msgBuffer.length} ` +
|
|
428
|
+
`sampleStartIdx=${sampleStart} sampleEndIdx=${sampleEnd !== null && sampleEnd !== void 0 ? sampleEnd : "?"} ` +
|
|
429
|
+
`availableInSample=${availableInSample} availableInBuffer=${availableInBuffer} ` +
|
|
430
|
+
`sampleHex=${sampleHex} ` +
|
|
431
|
+
`pollRespMetadata.b=${pollRespMetadata === null || pollRespMetadata === void 0 ? void 0 : pollRespMetadata.b} ` +
|
|
432
|
+
`schema=${JSON.stringify(pollRespMetadata === null || pollRespMetadata === void 0 ? void 0 : pollRespMetadata.a)}`);
|
|
433
|
+
}
|
|
382
434
|
signExtend(value, mask) {
|
|
383
435
|
const signBitMask = (mask + 1) >> 1;
|
|
384
436
|
const signBit = value & signBitMask;
|