UNPKG

@robotical/roboticaljs

Version:

Javascript/TS library for Robotical products

221 lines (191 loc) 8.15 kB
import { RaftConnector, RaftEventFn, RaftLog, RaftSystemType, RaftSystemUtils, RaftSysTypeManager } from "@robotical/raftjs"; import SettingsManager from "./SettingsManager"; import { SystemTypeCog, SystemTypeMarty } from "./main"; import SystemTypeGeneric from "./SystemTypeGeneric/SystemTypeGeneric"; const sysTypeManager = RaftSysTypeManager.getInstance(); const settingsManager = SettingsManager.getInstance(); try { sysTypeManager.addSystemType('Cog', () => new SystemTypeCog()); } catch (e) { RaftLog.warn(`ConnManager - failed to add system type Cog: ${e}`); } try { sysTypeManager.addSystemType('RIC', () => new SystemTypeMarty()); } catch (e) { RaftLog.warn(`ConnManager - failed to add system type RIC: ${e}`); } try { sysTypeManager.addDefaultSystemType(() => new SystemTypeGeneric()); } catch (e) { RaftLog.warn(`ConnManager - failed to add default system type: ${e}`); } const connectedDevices: BluetoothDevice[] = []; export default class ConnManager { // SERVICE UUIDS for external use static RICUUID = "aa76677e-9cfd-4626-a510-0d305be57c8d"; static COGUUID = "da903f65-d5c2-4f4d-a065-d1aade7af874"; // Singleton private static _instance: ConnManager; // Connector private _connector = new RaftConnector(async (systemUtils: RaftSystemUtils) => { const systemInfo = await systemUtils.getSystemInfo(); const sysType = sysTypeManager.createSystemType(systemInfo.SystemName) || sysTypeManager.createDefaultSystemType(); sysType?.deviceMgrIF.setMaxDataPointsToStore(settingsManager.getSetting("maxDatapointsToStore")); return sysType; }); // Callback on connection event private _onConnectionEvent: RaftEventFn | null = null; // Get instance public static getInstance(): ConnManager { if (!ConnManager._instance) { ConnManager._instance = new ConnManager(); } return ConnManager._instance; } // Set connection event listener public setConnectionEventListener(listener: RaftEventFn) { this._onConnectionEvent = listener; } // Check if connected public isConnected(): boolean { return this._connector.isConnected(); } public getConnector(): RaftConnector { return this._connector; } /** * @brief Like getBleDevice but the names to ignore are passed in as an array * @param namesToIgnore Array of names to ignore * @returns A promise that resolves to a BluetoothDevice or null if no device is found */ public async getBleDevicesWithGivenNamesToIgnore(namesToIgnore: { name: string; }[]): Promise<BluetoothDevice | null> { try { const dev = await navigator.bluetooth.requestDevice({ filters: [{ services: [ConnManager.RICUUID] }, { services: [ConnManager.COGUUID] }], optionalServices: [ConnManager.RICUUID, ConnManager.COGUUID], // @ts-ignore exclusionFilters: namesToIgnore.length > 0 ? namesToIgnore : undefined, }); connectedDevices.push(dev); return dev; } catch (e) { RaftLog.error(`getBleDevice - failed to get device ${e}`); return null; } } private async getBleDevice(uuids: string[]): Promise<BluetoothDevice | null> { const filtersArray = uuids.map((uuid) => ({ services: [uuid] })); const namesToIgnore: { name: string }[] = []; console.log("connectedDevices", connectedDevices); [...connectedDevices].forEach((dev) => { console.log("dev.name", dev.name); console.log("isDevConnected", dev.gatt?.connected); const isDevConnected = dev.gatt?.connected; if (isDevConnected && dev.name) { namesToIgnore.push(({ name: dev.name })); } else { connectedDevices.splice(connectedDevices.indexOf(dev), 1); } }); console.log("connectedDevices after filtering", connectedDevices); console.log("namesToIgnore", namesToIgnore); try { const dev = await navigator.bluetooth.requestDevice({ filters: filtersArray, optionalServices: [ConnManager.RICUUID, ConnManager.COGUUID], // @ts-ignore exclusionFilters: namesToIgnore.length > 0 ? namesToIgnore : undefined, }); connectedDevices.push(dev); return dev; } catch (e) { RaftLog.error(`getBleDevice - failed to get device ${e}`); return null; } } // Get system type public getSystemType(): RaftSystemType | null { return this._connector.getSystemType(); } public async initializeChannel_phoneBLE(): Promise<boolean> { // Hook up the connector this._connector.setEventListener((evtType, eventEnum, eventName, eventData) => { RaftLog.verbose(`ConnManager - event ${eventName}`); if (this._onConnectionEvent) { this._onConnectionEvent(evtType, eventEnum, eventName, eventData); } }); ///// return this._connector.initializeChannel("PhoneBLE"); } // Connect public async connect(method: string, locator: string | object, uuids: string[]): Promise<boolean> { this._connector.setEventListener((evtType, eventEnum, eventName, eventData) => { RaftLog.verbose(`ConnManager - event ${eventName}`); if (this._onConnectionEvent) { this._onConnectionEvent(evtType, eventEnum, eventName, eventData); } }); await this._connector.initializeChannel(method); // Set the connector websocket suffix if (method === "WebBLE") { const dev = await this.getBleDevice(uuids); return this._connector.connect(dev as object); } return this._connector.connect(locator); } // Disconnect public disconnect(): Promise<void> { return this._connector.disconnect(); } /////////////////////////////////////////////////////////////////////////////////// /// @brief Generate a UUID for service filtering based on device serial number /// @param baseUUID Base UUID string (e.g., "aa76677e-9cfd-4626-0000-000000000000") /// @param serialNo Serial number as an ASCII string (e.g., "1234567890123456") /// @returns Modified UUID string public static generateServiceFilterUUID(serialNo: string, baseUUID: string = "aa76677e-9cfd-4626-0000-000000000000"): string { const UUID_128_BYTES = 16; // Convert UUID string to byte array let uuidBytes = ConnManager.uuidToByteArray(baseUUID); // Convert serial number assuming it is decimal (or hex) digits to bytes let serialBytes = ConnManager.hexStringToBytes(serialNo); // Limit to 16 bytes (UUID size) const bytesToProc = Math.min(serialBytes.length, UUID_128_BYTES); // console.log(`generateServiceFilterUUID - serialBCD: ${serialBCD} bytesToProc: ${bytesToProc}`); // XOR the serial BCD bytes with the UUID bytes for (let i = 0; i < bytesToProc; i++) { uuidBytes[15 - i] ^= serialBytes[bytesToProc - 1 - i]; } // Convert back to UUID string format return ConnManager.byteArrayToUUID(uuidBytes); } ///////////////////////////////////////////////////////////////////////////// /// @brief Convert UUID string to byte array (Big Endian order) public static uuidToByteArray(uuid: string): Uint8Array { return new Uint8Array( uuid.replace(/-/g, "") // Remove dashes .match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)) // Convert hex pairs to bytes ); } ///////////////////////////////////////////////////////////////////////////// /// @brief Convert byte array back to UUID string public static byteArrayToUUID(bytes: Uint8Array): string { return [...bytes] .map(b => b.toString(16).padStart(2, "0")) // Convert to hex .join("") .replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5"); // Format as UUID } ///////////////////////////////////////////////////////////////////////////// /// @brief Convert an hex string to bytes /// @param hex string - e.g. "1234567890123456" -> [0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56] /// @returns byte array public static hexStringToBytes(hex: string): Uint8Array { // Pad to ensure even number of characters if (hex.length % 2 !== 0) { hex = "0" + hex; } return new Uint8Array( hex.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || [] ); } }