Initial commit
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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
|
||||
* 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 = 'a0b1c2d3-e4f5-6789-abcd-ef0123456700';
|
||||
const CMD_UUID = 'a0b1c2d3-e4f5-6789-abcd-ef0123456701';
|
||||
const STATUS_UUID = 'a0b1c2d3-e4f5-6789-abcd-ef0123456702';
|
||||
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() {
|
||||
if (!navigator.bluetooth) {
|
||||
throw new Error('Web Bluetooth API not available. Use Chrome/Edge over HTTPS or localhost.');
|
||||
}
|
||||
|
||||
emit('connecting');
|
||||
|
||||
device = await navigator.bluetooth.requestDevice({
|
||||
filters: [{ name: DEVICE_NAME }],
|
||||
optionalServices: [SERVICE_UUID],
|
||||
});
|
||||
|
||||
device.addEventListener('gattserverdisconnected', onDisconnected);
|
||||
|
||||
server = await device.gatt.connect();
|
||||
|
||||
const service = await server.getPrimaryService(SERVICE_UUID);
|
||||
|
||||
cmdChar = await service.getCharacteristic(CMD_UUID);
|
||||
|
||||
// Status notifications (optional – firmware may not have this char yet)
|
||||
try {
|
||||
statusChar = await service.getCharacteristic(STATUS_UUID);
|
||||
await statusChar.startNotifications();
|
||||
statusChar.addEventListener('characteristicvaluechanged', onStatusNotification);
|
||||
} catch {
|
||||
console.warn('[BLE] Status characteristic not available – notifications disabled');
|
||||
}
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user