← Home
Docs/Software/Web Bluetooth & Dashboard

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.