- 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.
184 lines
4.4 KiB
JavaScript
184 lines
4.4 KiB
JavaScript
const decoder = new TextDecoder();
|
|
|
|
let socket = null;
|
|
let lineBuffer = '';
|
|
let commandQueue = Promise.resolve();
|
|
|
|
const lineListeners = new Set();
|
|
|
|
function emitLine(line) {
|
|
for (const listener of lineListeners) {
|
|
listener(line);
|
|
}
|
|
}
|
|
|
|
function parseIncomingChunk(chunk) {
|
|
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 handleSocketMessage(event) {
|
|
if (typeof event.data === 'string') {
|
|
parseIncomingChunk(event.data);
|
|
return;
|
|
}
|
|
|
|
if (event.data instanceof ArrayBuffer) {
|
|
parseIncomingChunk(decoder.decode(event.data));
|
|
}
|
|
}
|
|
|
|
function normalizeCommand(command) {
|
|
return command.endsWith('\r') ? command : `${command}\r`;
|
|
}
|
|
|
|
function enqueueCommand(fn) {
|
|
const run = commandQueue.then(fn, fn);
|
|
commandQueue = run.catch(() => {});
|
|
return run;
|
|
}
|
|
|
|
function ensureSocketReady() {
|
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
throw new Error('EggDuino WiFi socket is not connected');
|
|
}
|
|
}
|
|
|
|
function buildSocketUrl(options) {
|
|
if (options.url) {
|
|
return options.url;
|
|
}
|
|
|
|
const host = options.host;
|
|
if (!host) {
|
|
throw new Error('Missing EggDuino host');
|
|
}
|
|
|
|
const secure = options.secure === true;
|
|
const protocol = secure ? 'wss' : 'ws';
|
|
const port = Number(options.port ?? 1337);
|
|
const path = options.path ?? '/';
|
|
|
|
return `${protocol}://${host}:${port}${path}`;
|
|
}
|
|
|
|
export async function connectEggDuinoWifi(options = {}) {
|
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
|
|
if (socket && socket.readyState === WebSocket.CONNECTING) {
|
|
await new Promise((resolve, reject) => {
|
|
const onOpen = () => {
|
|
socket.removeEventListener('error', onError);
|
|
resolve();
|
|
};
|
|
const onError = () => {
|
|
socket.removeEventListener('open', onOpen);
|
|
reject(new Error('EggDuino WiFi socket failed while connecting'));
|
|
};
|
|
socket.addEventListener('open', onOpen, { once: true });
|
|
socket.addEventListener('error', onError, { once: true });
|
|
});
|
|
return;
|
|
}
|
|
|
|
const url = buildSocketUrl(options);
|
|
socket = new WebSocket(url);
|
|
socket.binaryType = 'arraybuffer';
|
|
|
|
socket.addEventListener('message', handleSocketMessage);
|
|
socket.addEventListener('close', () => {
|
|
socket = null;
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
const onOpen = () => {
|
|
socket.removeEventListener('error', onError);
|
|
resolve();
|
|
};
|
|
|
|
const onError = () => {
|
|
socket.removeEventListener('open', onOpen);
|
|
socket?.close();
|
|
socket = null;
|
|
reject(new Error(`EggDuino WiFi socket connection failed: ${url}`));
|
|
};
|
|
|
|
socket.addEventListener('open', onOpen, { once: true });
|
|
socket.addEventListener('error', onError, { once: true });
|
|
});
|
|
}
|
|
|
|
export function disconnectEggDuinoWifi() {
|
|
if (!socket) {
|
|
return;
|
|
}
|
|
|
|
socket.removeEventListener('message', handleSocketMessage);
|
|
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
socket.close();
|
|
}
|
|
socket = null;
|
|
}
|
|
|
|
export async function sendEggBotCommand(command) {
|
|
ensureSocketReady();
|
|
socket.send(normalizeCommand(command));
|
|
}
|
|
|
|
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);
|
|
}
|