A Web Bluetooth Template
If you want to build your own interface, here is a minimal JavaScript template that connects over Web Bluetooth, parses messages, and transmits commands:
Connecting to the Car
js
const SERVICE_UUID = "19b10000-e8f2-537e-4f6c-d104768a1214";
const STATUS_CHAR = "19b10001-e8f2-537e-4f6c-d104768a1214";
const CMD_CHAR = "19b10002-e8f2-537e-4f6c-d104768a1214";
let bleDevice = null;
let cmdChar = null;
async function connectToCar() {
try {
// 1. Prompt user to choose the Bluetooth device
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ namePrefix: "EVDrive" }],
optionalServices: [SERVICE_UUID],
});
bleDevice.addEventListener("gattserverdisconnected", () => {
cmdChar = null;
console.log("Car disconnected");
});
// 2. Connect to the GATT server
const server = await bleDevice.gatt.connect();
const service = await server.getPrimaryService(SERVICE_UUID);
// 3. Listen for notifications from the car
const statusChar = await service.getCharacteristic(STATUS_CHAR);
await statusChar.startNotifications();
statusChar.addEventListener("characteristicvaluechanged", handleIncomingData);
// 4. Get the characteristic used for sending commands
cmdChar = await service.getCharacteristic(CMD_CHAR);
console.log("Connected to car!");
} catch (err) {
console.error("Connection failed:", err);
}
}
Reading Incoming Messages
js
function handleIncomingData(event) {
// Decode raw bytes to text
const message = new TextDecoder().decode(event.target.value).trim();
// Human-readable events: log them and stop
if (message.startsWith("STATUS:") || message.startsWith("STOP:")) {
console.log("Car says:", message);
return;
}
// CFG readback starts with "CFG:", so strip it, then parse like telemetry
const body = message.startsWith("CFG:") ? message.slice(4) : message;
// Split "KEY:value KEY:value ..." into an object
const values = {};
for (const token of body.split(" ")) {
const i = token.indexOf(":");
if (i > 0) values[token.slice(0, i)] = token.slice(i + 1);
}
if (message.startsWith("CFG:")) {
console.log("Config:", values); // { DF: "7.000", KP: "28.0", ... }
} else {
console.log("Phase:", values.P);
console.log("Distance:", parseFloat(values.D), "m");
console.log("Heading:", parseFloat(values.H), "deg");
console.log("Battery:", parseFloat(values.B), "V");
}
}
Sending Commands to the Car
js
async function sendCommand(commandText) {
if (!cmdChar) {
console.warn("Not connected to car");
return;
}
// Encode string into bytes and write to the command characteristic
const data = new TextEncoder().encode(commandText.trim());
await cmdChar.writeValue(data);
console.log("Sent command:", commandText);
}
// Example usage:
// sendCommand("DIST_F:7.0");
// sendCommand("TIME_F:6.5");
// sendCommand("START");
Tip: Every setter makes the car echo its whole configuration. If your interface has editable fields, don't let the echo overwrite values the user has typed but not yet sent. The reference dashboard solves this by tracking which fields are "dirty."
6. General Tips
- Use what works best for your team: You don't need fancy graphs or full web apps. Even a basic HTML page with a Connect button, a few input boxes, and a Send button works.
- Serial Monitor fallback: Always make sure your car can still be operated with the physical start button and a USB cable in case you have Bluetooth problems.
- Offline mode reminder: When testing standalone autonomous navigation, flip the wireless switch OFF, then press Reset, and confirm the wireless LED is dark before launching your run.