UNPKG

@mmote/niimblue-node

Version:

Headless clients for niimbluelib. Command line interface, simple REST server are also included.

168 lines (167 loc) 6.24 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NiimbotHeadlessBleClient = void 0; const noble_1 = __importDefault(require("@abandonware/noble")); const niimbluelib_1 = require("@mmote/niimbluelib"); class NiimbotHeadlessBleClient extends niimbluelib_1.NiimbotAbstractClient { constructor() { super(); this.addr = ""; } /** Set device mac address or name for connect */ setAddress(address) { this.addr = address; } static async waitAdapterReady() { if (noble_1.default._state === "poweredOn") { return; } return new Promise((resolve, reject) => { let timer; noble_1.default.on("stateChange", async (state) => { clearTimeout(timer); if (state === "poweredOn") { resolve(); } else { reject(new Error(`BLE state is ${state}`)); } }); timer = setTimeout(() => { reject(new Error("Can't init BLE")); }, 5000); }); } static async scan(timeoutMs = 5000) { await NiimbotHeadlessBleClient.waitAdapterReady(); return new Promise((resolve, reject) => { const peripherals = []; let timer; noble_1.default.on("discover", async (peripheral) => { peripherals.push({ address: peripheral.address, name: peripheral.advertisement.localName || "unknown", }); }); noble_1.default.startScanning([], false, (error) => { if (error) { clearTimeout(timer); reject(error); } }); timer = setTimeout(() => { noble_1.default.stopScanning(); resolve(peripherals); }, timeoutMs ?? 5000); }); } async getDevice(address, timeoutMs = 5000) { await NiimbotHeadlessBleClient.waitAdapterReady(); return new Promise((resolve, reject) => { let timer; noble_1.default.on("discover", async (peripheral) => { if (peripheral.address === address.toLowerCase() || peripheral.advertisement.localName === address) { clearTimeout(timer); resolve(peripheral); } }); noble_1.default.startScanning([], false, (error) => { if (error) reject(error); }); timer = setTimeout(() => { noble_1.default.stopScanning(); reject(new Error("Device not found")); }, timeoutMs ?? 5000); }); } async connectToDevice(address, timeoutMs = 5000) { const periph = await this.getDevice(address, timeoutMs); await periph.connectAsync(); const services = await periph.discoverServicesAsync(); let channelCharacteristic; for (const service of services) { if (service.uuid.length < 5) { continue; } const characteristics = await service.discoverCharacteristicsAsync(); const suitableCharacteristic = characteristics.find((ch) => ch.properties.includes("notify") && ch.properties.includes("writeWithoutResponse")); if (suitableCharacteristic) { channelCharacteristic = suitableCharacteristic; break; } } if (channelCharacteristic === undefined) { await periph.disconnectAsync(); throw new Error("Unable to find suitable channel characteristic"); } periph.on("disconnect", () => { this.stopHeartbeat(); this.emit("disconnect", new niimbluelib_1.DisconnectEvent()); this.device = undefined; this.channel = undefined; }); channelCharacteristic.on("read", (data, isNotification) => { if (isNotification) this.processRawPacket(new Uint8Array(data)); }); channelCharacteristic.subscribeAsync(); this.channel = channelCharacteristic; this.device = periph; } async connect() { await this.disconnect(); if (!this.addr) { throw new Error("Device address or name not set"); } await this.connectToDevice(this.addr); try { await this.initialNegotiate(); await this.fetchPrinterInfo(); } catch (e) { console.error("Unable to fetch printer info."); console.error(e); } const result = { deviceName: this.device.advertisement.localName ?? this.addr, result: this.info.connectResult ?? niimbluelib_1.ConnectResult.FirmwareErrors, }; this.emit("connect", new niimbluelib_1.ConnectEvent(result)); return result; } isConnected() { return this.device !== undefined && this.channel !== undefined; } async disconnect() { this.stopHeartbeat(); if (this.device !== undefined) { await this.device.disconnectAsync(); this.emit("disconnect", new niimbluelib_1.DisconnectEvent()); } this.device = undefined; this.channel = undefined; } async sendRaw(data, force) { const send = async () => { if (!this.isConnected()) { this.disconnect(); throw new Error("Disconnected"); } await niimbluelib_1.Utils.sleep(this.packetIntervalMs); await this.channel.writeAsync(Buffer.from(data), true); this.emit("rawpacketsent", new niimbluelib_1.RawPacketSentEvent(data)); }; if (force) { await send(); } else { await this.mutex.runExclusive(send); } } } exports.NiimbotHeadlessBleClient = NiimbotHeadlessBleClient;