Files
ESP32-WijiBoard/web/js/ble.js
T

154 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WijiBoard BLE Singleton
* web/js/ble.js
*
* Provides a singleton BLE object with:
* BLE.connect() → connects to device, gets characteristics
* BLE.disconnect() → disconnects
* BLE.write(cmd) → writes ASCII command (write-without-response)
* BLE.isConnected() → boolean
* BLE.on(event, cb) → subscribe to 'connected' | 'disconnected' | 'status'
* BLE.off(event, cb) → unsubscribe
*
* Command characteristic (WRITE_NR):
* S1+<n> → step motor 1 clockwise n steps
* S1-<n> → step motor 1 counter-clockwise n steps
* S2+<n> → step motor 2 clockwise n steps
* S2-<n> → step motor 2 counter-clockwise n steps
* SPD:<n> → set max speed (steps/sec)
* ACC:<n> → set acceleration (steps/sec²)
* HOME → zero both steppers
* DISABLE → disable steppers (un-hold)
* POS → request position report
*
* Status characteristic (NOTIFY):
* P:<s1>,<s2> → current step positions for motor 1 & 2
*/
// ── UUIDs must match src/main.cpp exactly ──────────────────────
const SERVICE_UUID = '18f3b235-9831-4c75-8ec0-210469b820a0';
const CMD_UUID = 'cd083b06-4447-4cf3-a7c3-322ecf802ce4';
const STATUS_UUID = '82e38c5b-d3ab-41d1-861c-b84dc6bb1e03';
const DEVICE_NAME = 'WijiBoard';
const BLE = (() => {
// ── Internal state ──────────────────────────────────────────
let device = null;
let server = null;
let cmdChar = null;
let statusChar = null;
const encoder = new TextEncoder();
// ── Tiny event emitter ──────────────────────────────────────
const listeners = {};
function emit(event, ...args) {
(listeners[event] || []).forEach(cb => {
try { cb(...args); } catch (e) { console.error('[BLE] listener error', e); }
});
}
function on(event, cb) {
if (!listeners[event]) listeners[event] = [];
listeners[event].push(cb);
}
function off(event, cb) {
if (!listeners[event]) return;
listeners[event] = listeners[event].filter(fn => fn !== cb);
}
// ── Disconnect handler ──────────────────────────────────────
function onDisconnected() {
console.log('[BLE] Device disconnected');
device = null;
server = null;
cmdChar = null;
statusChar = null;
emit('disconnected');
}
// ── Status notification handler ─────────────────────────────
function onStatusNotification(event) {
const value = new TextDecoder().decode(event.target.value);
emit('status', value.trim());
}
// ── connect() ───────────────────────────────────────────────
async function connect() {
console.log('[BLE] connect() called');
if (!navigator.bluetooth) {
throw new Error('Web Bluetooth API not available. Use Chrome/Edge over HTTPS or localhost.');
}
emit('connecting');
console.log('[BLE] Requesting device...');
device = await navigator.bluetooth.requestDevice({
filters: [{ name: DEVICE_NAME }],
optionalServices: [SERVICE_UUID],
});
console.log('[BLE] Device selected:', device.name);
device.addEventListener('gattserverdisconnected', onDisconnected);
console.log('[BLE] Connecting to GATT server...');
server = await device.gatt.connect();
console.log('[BLE] GATT server connected');
console.log('[BLE] Getting primary service:', SERVICE_UUID);
const service = await server.getPrimaryService(SERVICE_UUID);
console.log('[BLE] Primary service found');
console.log('[BLE] Getting command characteristic:', CMD_UUID);
cmdChar = await service.getCharacteristic(CMD_UUID);
console.log('[BLE] Command characteristic found');
// Status notifications (optional firmware may not have this char yet)
try {
console.log('[BLE] Getting status characteristic:', STATUS_UUID);
statusChar = await service.getCharacteristic(STATUS_UUID);
console.log('[BLE] Starting notifications...');
await statusChar.startNotifications();
statusChar.addEventListener('characteristicvaluechanged', onStatusNotification);
console.log('[BLE] Notifications started');
} catch (err) {
console.warn('[BLE] Status characteristic not available notifications disabled', err);
}
console.log('[BLE] Connection fully established, emitting connected');
emit('connected', device.name);
}
// ── disconnect() ────────────────────────────────────────────
async function disconnect() {
if (device?.gatt?.connected) {
device.gatt.disconnect();
}
}
// ── write() ─────────────────────────────────────────────────
async function write(cmd) {
if (!cmdChar) throw new Error('BLE not connected');
await cmdChar.writeValueWithoutResponse(encoder.encode(cmd));
emit('sent', cmd);
}
// ── isConnected() ───────────────────────────────────────────
function isConnected() {
return !!(device?.gatt?.connected && cmdChar);
}
// ── getDeviceName() ─────────────────────────────────────────
function getDeviceName() {
return device?.name ?? null;
}
return {
on, off, connect, disconnect, write, isConnected, getDeviceName,
SERVICE_UUID, CMD_UUID, STATUS_UUID, DEVICE_NAME,
};
})();
export default BLE;