- Added readSerial(Stream &stream) method to allow reading from any Stream-compatible transport. - Introduced readChar(char inChar) method for processing individual characters. - Updated command matching logic to enhance debugging output. - Improved buffer handling and command execution flow. Enhance platformio.ini for better compatibility and added libraries - Added NimBLE-Arduino and WebSockets libraries for BLE and WiFi support. - Updated upload and monitor ports for better compatibility with macOS. Integrate WiFi and BLE protocol interfaces - Implemented startWebInterface() to initialize WiFi protocol alongside existing web server. - Added BLE support with a new EggBot BLE Serial Protocol for command handling over BLE. - Created WebSocket server for WiFi communication, maintaining compatibility with existing command protocols. Refactor command handling in Functions.cpp - Replaced direct Serial.print calls with protocolWrite for consistent output handling. - Updated command registration to use a lambda function for better readability and maintainability. Add documentation for EggBot protocols - Created separate markdown files for BLE, WiFi, and Serial protocols detailing command structures and usage. - Provided examples of command transactions for better developer guidance. Implement BLE and WiFi protocol handling in respective source files - Developed BLE_Interface.cpp for managing BLE connections and data transmission. - Created WiFi_Protocol.cpp for handling WebSocket communication and data reception.
186 lines
4.6 KiB
JavaScript
186 lines
4.6 KiB
JavaScript
const SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
|
|
const RX_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';
|
|
const TX_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e';
|
|
|
|
const encoder = new TextEncoder();
|
|
const decoder = new TextDecoder();
|
|
|
|
let device = null;
|
|
let server = null;
|
|
let rxCharacteristic = null;
|
|
let txCharacteristic = null;
|
|
let lineBuffer = '';
|
|
let commandQueue = Promise.resolve();
|
|
|
|
const lineListeners = new Set();
|
|
|
|
function emitLine(line) {
|
|
for (const listener of lineListeners) {
|
|
listener(line);
|
|
}
|
|
}
|
|
|
|
function handleNotification(event) {
|
|
const dataView = event.target.value;
|
|
const chunk = decoder.decode(dataView, { stream: true });
|
|
lineBuffer += chunk;
|
|
|
|
while (true) {
|
|
const delimiterIndex = lineBuffer.indexOf('\r\n');
|
|
if (delimiterIndex < 0) {
|
|
break;
|
|
}
|
|
|
|
const line = lineBuffer.slice(0, delimiterIndex);
|
|
lineBuffer = lineBuffer.slice(delimiterIndex + 2);
|
|
emitLine(line);
|
|
}
|
|
}
|
|
|
|
function handleDisconnected() {
|
|
server = null;
|
|
rxCharacteristic = null;
|
|
if (txCharacteristic) {
|
|
txCharacteristic.removeEventListener('characteristicvaluechanged', handleNotification);
|
|
}
|
|
txCharacteristic = null;
|
|
}
|
|
|
|
async function writeToRxCharacteristic(payloadBytes) {
|
|
if (!rxCharacteristic) {
|
|
throw new Error('EggDuino is not connected');
|
|
}
|
|
|
|
if (typeof rxCharacteristic.writeValueWithoutResponse === 'function') {
|
|
await rxCharacteristic.writeValueWithoutResponse(payloadBytes);
|
|
return;
|
|
}
|
|
|
|
if (typeof rxCharacteristic.writeValueWithResponse === 'function') {
|
|
await rxCharacteristic.writeValueWithResponse(payloadBytes);
|
|
return;
|
|
}
|
|
|
|
await rxCharacteristic.writeValue(payloadBytes);
|
|
}
|
|
|
|
function normalizeCommand(command) {
|
|
return command.endsWith('\r') ? command : `${command}\r`;
|
|
}
|
|
|
|
function enqueueCommand(fn) {
|
|
const run = commandQueue.then(fn, fn);
|
|
commandQueue = run.catch(() => {});
|
|
return run;
|
|
}
|
|
|
|
export async function connectEggDuino() {
|
|
if (server?.connected && rxCharacteristic && txCharacteristic) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
device = await navigator.bluetooth.requestDevice({
|
|
filters: [{ services: [SERVICE_UUID] }],
|
|
optionalServices: [SERVICE_UUID],
|
|
});
|
|
} catch (error) {
|
|
throw new Error(`BLE request failed: ${error.message}`);
|
|
}
|
|
|
|
device.addEventListener('gattserverdisconnected', handleDisconnected);
|
|
|
|
try {
|
|
server = await device.gatt.connect();
|
|
} catch (error) {
|
|
throw new Error(`BLE gatt connect failed: ${error.message}`);
|
|
}
|
|
|
|
let service;
|
|
try {
|
|
service = await server.getPrimaryService(SERVICE_UUID);
|
|
} catch (error) {
|
|
throw new Error(`BLE service lookup failed: ${error.message}`);
|
|
}
|
|
|
|
try {
|
|
rxCharacteristic = await service.getCharacteristic(RX_UUID);
|
|
txCharacteristic = await service.getCharacteristic(TX_UUID);
|
|
} catch (error) {
|
|
throw new Error(`BLE characteristic lookup failed: ${error.message}`);
|
|
}
|
|
|
|
try {
|
|
await txCharacteristic.startNotifications();
|
|
txCharacteristic.addEventListener('characteristicvaluechanged', handleNotification);
|
|
} catch (error) {
|
|
throw new Error(`BLE notifications setup failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
export function disconnectEggDuino() {
|
|
if (!device) {
|
|
return;
|
|
}
|
|
|
|
if (txCharacteristic) {
|
|
txCharacteristic.removeEventListener('characteristicvaluechanged', handleNotification);
|
|
}
|
|
|
|
if (device.gatt?.connected) {
|
|
device.gatt.disconnect();
|
|
}
|
|
|
|
handleDisconnected();
|
|
}
|
|
|
|
export async function sendEggBotCommand(command) {
|
|
const normalized = normalizeCommand(command);
|
|
await writeToRxCharacteristic(encoder.encode(normalized));
|
|
}
|
|
|
|
export async function sendEggBotCommandExpectOk(command, timeoutMs = 2000) {
|
|
return enqueueCommand(() => new Promise(async (resolve, reject) => {
|
|
const collectedLines = [];
|
|
|
|
const timeout = setTimeout(() => {
|
|
lineListeners.delete(onResponseLine);
|
|
reject(new Error('EggBot response timeout'));
|
|
}, timeoutMs);
|
|
|
|
const onResponseLine = (line) => {
|
|
collectedLines.push(line);
|
|
|
|
if (line === 'OK') {
|
|
clearTimeout(timeout);
|
|
lineListeners.delete(onResponseLine);
|
|
resolve(collectedLines.slice(0, -1));
|
|
}
|
|
|
|
if (line === 'unknown CMD') {
|
|
clearTimeout(timeout);
|
|
lineListeners.delete(onResponseLine);
|
|
reject(new Error(`EggBot error: ${collectedLines.join(' | ')}`));
|
|
}
|
|
};
|
|
|
|
lineListeners.add(onResponseLine);
|
|
|
|
try {
|
|
await sendEggBotCommand(command);
|
|
} catch (error) {
|
|
clearTimeout(timeout);
|
|
lineListeners.delete(onResponseLine);
|
|
reject(error);
|
|
}
|
|
}));
|
|
}
|
|
|
|
export function onLine(callback) {
|
|
lineListeners.add(callback);
|
|
}
|
|
|
|
export function offLine(callback) {
|
|
lineListeners.delete(callback);
|
|
}
|