Skip to content

Commit 10dbda9

Browse files
committed
update to handle puppet over USB serial
This allows the gamepad and dashboard to work over USB serial. Moved common code to connections.ts
1 parent 6891971 commit 10dbda9

3 files changed

Lines changed: 184 additions & 77 deletions

File tree

src/connections/bluetoothconnection.ts

Lines changed: 4 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ export class BluetoothConnection extends Connection {
3939

4040
private Table: TableMgr | undefined = undefined;
4141

42-
// XPP Protocol constants for packet detection
43-
private readonly XPP_START_SEQ: number[] = [0xAA, 0x55];
44-
private readonly XPP_END_SEQ: number[] = [0x55, 0xAA];
45-
private xppBuffer: Uint8Array = new Uint8Array(0); // Buffer for incomplete XPP packets
46-
4742
constructor(connMgr: ConnectionMgr) {
4843
super();
4944
this.connMgr = connMgr;
@@ -181,72 +176,6 @@ export class BluetoothConnection extends Connection {
181176
}
182177

183178

184-
/**
185-
* Extracts complete XPP packets from the buffer and returns them.
186-
* XPP packet format: [0xAA 0x55] [Type] [Length] [Payload] [0x55 0xAA]
187-
* @param data - New data to add to the buffer
188-
* @returns Array of complete XPP packets
189-
*/
190-
private extractCompleteXPPPackets(data: Uint8Array): Uint8Array[] {
191-
// Add new data to buffer
192-
this.xppBuffer = this.concatUint8Arrays(this.xppBuffer, data);
193-
194-
const completePackets: Uint8Array[] = [];
195-
196-
while (this.xppBuffer.length >= 4) { // Minimum packet size is 4 bytes (start + type + length + end)
197-
// Look for start sequence [0xAA 0x55]
198-
const startIndex = this.xppBuffer.findIndex((val, idx) =>
199-
idx < this.xppBuffer.length - 1 &&
200-
val === this.XPP_START_SEQ[0] &&
201-
this.xppBuffer[idx + 1] === this.XPP_START_SEQ[1]
202-
);
203-
204-
if (startIndex === -1) {
205-
// No start sequence found, clear buffer
206-
this.xppBuffer = new Uint8Array(0);
207-
break;
208-
}
209-
210-
// Remove any data before the start sequence
211-
if (startIndex > 0) {
212-
this.xppBuffer = this.xppBuffer.subarray(startIndex);
213-
}
214-
215-
// Check if we have enough data for type and length (at least 4 bytes: start + type + length)
216-
if (this.xppBuffer.length < 4) {
217-
break; // Need more data
218-
}
219-
220-
// Extract length (byte 3 after start sequence)
221-
// Type is at byte 2, but we don't need it for packet extraction
222-
const payloadLength = this.xppBuffer[3];
223-
224-
// Calculate total packet size: start(2) + type(1) + length(1) + payload + end(2)
225-
const totalPacketSize = 2 + 1 + 1 + payloadLength + 2;
226-
227-
// Check if we have the complete packet
228-
if (this.xppBuffer.length < totalPacketSize) {
229-
break; // Need more data
230-
}
231-
232-
// Verify end sequence
233-
const endIndex = totalPacketSize - 2;
234-
if (this.xppBuffer[endIndex] === this.XPP_END_SEQ[0] &&
235-
this.xppBuffer[endIndex + 1] === this.XPP_END_SEQ[1]) {
236-
// Complete packet found
237-
const completePacket = this.xppBuffer.subarray(0, totalPacketSize);
238-
completePackets.push(completePacket);
239-
240-
// Remove processed packet from buffer
241-
this.xppBuffer = this.xppBuffer.subarray(totalPacketSize);
242-
} else {
243-
// Invalid end sequence, skip this start sequence and continue
244-
this.xppBuffer = this.xppBuffer.subarray(2);
245-
}
246-
}
247-
248-
return completePackets;
249-
}
250179

251180
/**
252181
* readWorker - this worker read data from the XRP robot
@@ -266,11 +195,10 @@ export class BluetoothConnection extends Connection {
266195
valuesD = await this.get2BLEData();
267196
if(valuesD != undefined) {
268197
// Extract complete XPP packets and only process those
269-
const completePackets = this.extractCompleteXPPPackets(valuesD);
270-
for (const packet of completePackets) {
271-
this.joyStick?.handleXPPMessage(packet);
272-
this.Table?.readFromDevice(packet);
273-
//NOTE: if there is one more of these then we should switch to a subscription model.
198+
// Note: regularData is ignored since bleDataReader only receives XPP packets
199+
const { packets } = this.extractCompleteXPPPackets(valuesD);
200+
for (const packet of packets) {
201+
this.processXPPPacket(packet, this.Table);
274202
}
275203
}
276204
}

src/connections/connection.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ConnectionMgr from "@/managers/connectionmgr";
22
import logger from "@/utils/logger";
33
import Joystick from '@/managers/joystickmgr';
4+
import TableMgr from '@/managers/tablemgr';
45

56

67
/**
@@ -50,6 +51,11 @@ abstract class Connection {
5051

5152
readonly XRP_SEND_BLOCK_SIZE: number = 250; // wired can handle 255 bytes, but BLE 5.0 is only 250
5253

54+
// XPP Protocol constants for packet detection
55+
protected readonly XPP_START_SEQ: number[] = [0xAA, 0x55];
56+
protected readonly XPP_END_SEQ: number[] = [0x55, 0xAA];
57+
protected xppBuffer: Uint8Array = new Uint8Array(0); // Buffer for incomplete XPP packets
58+
5359
joyStick: Joystick | undefined;
5460

5561
constructor() {
@@ -107,6 +113,162 @@ abstract class Connection {
107113
return result;
108114
}
109115

116+
/**
117+
* Extracts complete XPP packets from the buffer and returns them.
118+
* XPP packet format: [0xAA 0x55] [Type] [Length] [Payload] [0x55 0xAA]
119+
* @param data - New data to add to the buffer
120+
* @returns Array of complete XPP packets
121+
*
122+
* regularData anything that is not part of a XPP packet
123+
* packets are the complete XPP packets
124+
* anything before start sequence is regular data
125+
* anything after an end with no other start sequence is regular data
126+
*
127+
*/
128+
protected extractCompleteXPPPackets(data: Uint8Array): { packets: Uint8Array[], regularData: Uint8Array } {
129+
// Add new data to buffer
130+
this.xppBuffer = this.concatUint8Arrays(this.xppBuffer, data);
131+
let regularData: Uint8Array = new Uint8Array(0);
132+
133+
const completePackets: Uint8Array[] = [];
134+
135+
// Process buffer to extract packets and regular data
136+
while (this.xppBuffer.length > 0) {
137+
// If buffer is too small to be a complete packet, check if we can pass through regular data
138+
if (this.xppBuffer.length < 4) {
139+
// Check if buffer could potentially start an XPP packet
140+
if (this.xppBuffer.length === 1) {
141+
// Single byte: if it's 0xAA, might be start of XPP, keep it
142+
// Otherwise, it's definitely regular data
143+
if (this.xppBuffer[0] !== this.XPP_START_SEQ[0]) {
144+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer);
145+
this.xppBuffer = new Uint8Array(0);
146+
}
147+
break; // Need more data to determine
148+
} else if (this.xppBuffer.length === 2) {
149+
// Two bytes: check if it's [0xAA, 0x55]
150+
if (this.xppBuffer[0] === this.XPP_START_SEQ[0] &&
151+
this.xppBuffer[1] === this.XPP_START_SEQ[1]) {
152+
// Could be start of XPP packet, keep it
153+
break; // Need more data
154+
} else if (this.xppBuffer[0] === this.XPP_START_SEQ[0]) {
155+
// First byte is 0xAA but second isn't 0x55, pass first byte as regular data
156+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer.subarray(0, 1));
157+
this.xppBuffer = this.xppBuffer.subarray(1);
158+
// Continue processing the remaining byte
159+
} else {
160+
// First byte is not 0xAA, both bytes are regular data
161+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer);
162+
this.xppBuffer = new Uint8Array(0);
163+
break;
164+
}
165+
} else if (this.xppBuffer.length === 3) {
166+
// Three bytes: check if it starts with [0xAA, 0x55]
167+
if (this.xppBuffer[0] === this.XPP_START_SEQ[0] &&
168+
this.xppBuffer[1] === this.XPP_START_SEQ[1]) {
169+
// Could be start of XPP packet, keep it
170+
break; // Need more data
171+
} else if (this.xppBuffer[0] === this.XPP_START_SEQ[0]) {
172+
// First byte is 0xAA but second isn't 0x55, pass first byte as regular data
173+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer.subarray(0, 1));
174+
this.xppBuffer = this.xppBuffer.subarray(1);
175+
// Continue processing the remaining 2 bytes
176+
} else {
177+
// First byte is not 0xAA, check if second byte is 0xAA (could be start of XPP)
178+
if (this.xppBuffer[1] === this.XPP_START_SEQ[0]) {
179+
// Second byte might be start, pass first byte as regular data
180+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer.subarray(0, 1));
181+
this.xppBuffer = this.xppBuffer.subarray(1);
182+
// Continue processing the remaining 2 bytes
183+
} else {
184+
// No potential XPP start, pass all as regular data
185+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer);
186+
this.xppBuffer = new Uint8Array(0);
187+
break;
188+
}
189+
}
190+
}
191+
continue; // Re-check buffer after modifications
192+
}
193+
194+
// Buffer has at least 4 bytes, look for start sequence [0xAA 0x55]
195+
const startIndex = this.xppBuffer.findIndex((val, idx) =>
196+
idx < this.xppBuffer.length - 1 &&
197+
val === this.XPP_START_SEQ[0] &&
198+
this.xppBuffer[idx + 1] === this.XPP_START_SEQ[1]
199+
);
200+
201+
if (startIndex === -1) {
202+
// No start sequence found
203+
// Check if last byte is 0xAA (might be start of next XPP packet)
204+
const lastByte = this.xppBuffer[this.xppBuffer.length - 1];
205+
if (lastByte === this.XPP_START_SEQ[0]) {
206+
// Keep last byte in buffer, pass rest as regular data
207+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer.subarray(0, this.xppBuffer.length - 1));
208+
this.xppBuffer = this.xppBuffer.subarray(this.xppBuffer.length - 1);
209+
} else {
210+
// No potential XPP start, clear buffer
211+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer);
212+
this.xppBuffer = new Uint8Array(0);
213+
}
214+
break;
215+
}
216+
217+
// Remove any data before the start sequence
218+
if (startIndex > 0) {
219+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer.subarray(0, startIndex));
220+
this.xppBuffer = this.xppBuffer.subarray(startIndex);
221+
}
222+
223+
// Check if we have enough data for type and length (at least 4 bytes: start + type + length)
224+
if (this.xppBuffer.length < 4) {
225+
break; // Need more data
226+
}
227+
228+
// Extract length (byte 3 after start sequence)
229+
// Type is at byte 2, but we don't need it for packet extraction
230+
const payloadLength = this.xppBuffer[3];
231+
232+
// Calculate total packet size: start(2) + type(1) + length(1) + payload + end(2)
233+
const totalPacketSize = 2 + 1 + 1 + payloadLength + 2;
234+
235+
// Check if we have the complete packet
236+
if (this.xppBuffer.length < totalPacketSize) {
237+
break; // Need more data
238+
}
239+
240+
// Verify end sequence
241+
const endIndex = totalPacketSize - 2;
242+
if (this.xppBuffer[endIndex] === this.XPP_END_SEQ[0] &&
243+
this.xppBuffer[endIndex + 1] === this.XPP_END_SEQ[1]) {
244+
// Complete packet found
245+
const completePacket = this.xppBuffer.subarray(0, totalPacketSize);
246+
completePackets.push(completePacket);
247+
248+
// Remove processed packet from buffer
249+
this.xppBuffer = this.xppBuffer.subarray(totalPacketSize);
250+
} else {
251+
// Invalid end sequence, skip this start sequence and continue
252+
regularData = this.concatUint8Arrays(regularData, this.xppBuffer.subarray(0, 2));
253+
this.xppBuffer = this.xppBuffer.subarray(2);
254+
}
255+
}
256+
257+
return { packets: completePackets, regularData };
258+
}
259+
260+
/**
261+
* Processes a complete XPP packet by routing it to the appropriate handlers.
262+
* @param packet - Complete XPP packet
263+
* @param tableMgr - Optional TableMgr instance for handling table-related XPP messages
264+
*/
265+
protected processXPPPacket(packet: Uint8Array, tableMgr?: TableMgr): void {
266+
this.joyStick?.handleXPPMessage(packet);
267+
tableMgr?.readFromDevice(packet);
268+
}
269+
270+
271+
110272
/**
111273
* readData - read data from the XRP connection
112274
* @param values

src/connections/usbconnection.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ConnectionMgr from '@/managers/connectionmgr';
33
import i18n from '@/utils/i18n';
44
import { ConnectionType } from '@/utils/types';
55
import Connection, { ConnectionState } from '@connections/connection';
6+
import TableMgr from '@/managers/tablemgr';
67

78
/**
89
* USB Connection - establish USB serial connection to the XRP Robot
@@ -12,6 +13,7 @@ export class USBConnection extends Connection {
1213
private port: SerialPort | undefined = undefined;
1314
private reader: ReadableStreamDefaultReader<Uint8Array> | undefined = undefined; // Reference to serial port reader, only one can be locked at a time
1415
private writer: WritableStreamDefaultWriter<Uint8Array> | undefined = undefined; // Reference to serial port writer, only one can be locked at a time
16+
private Table: TableMgr | undefined = undefined;
1517

1618
// Define USB connection constants
1719
readonly USB_VENDOR_ID_BETA: number = 11914; // For filtering ports during auto or manual selection
@@ -23,6 +25,9 @@ export class USBConnection extends Connection {
2325
super();
2426
this.connMgr = connMgr;
2527
this.isManualConnection = false;
28+
this.Table = new TableMgr();
29+
if(this.joyStick)
30+
this.joyStick.writeToDevice = this.writeToDevice.bind(this);
2631

2732
// setup USB connection listeners
2833
// Check if browser can use WebSerial
@@ -85,7 +90,19 @@ export class USBConnection extends Connection {
8590
this.reader.releaseLock();
8691
break;
8792
}
88-
this.readData(value);
93+
94+
// Extract XPP packets and regular data from the incoming stream
95+
const { packets, regularData } = this.extractCompleteXPPPackets(value);
96+
97+
// Process complete XPP packets
98+
for (const packet of packets) {
99+
this.processXPPPacket(packet, this.Table);
100+
}
101+
102+
// Pass any regular (non-XPP) data to readData for normal processing
103+
if (regularData.length > 0) {
104+
this.readData(regularData);
105+
}
89106
}
90107
}
91108
// eslint-disable-next-line @typescript-eslint/no-explicit-any

0 commit comments

Comments
 (0)