@mmote/niimblue-node
Version:
Headless clients for niimbluelib. Command line interface, simple REST server are also included.
168 lines (167 loc) • 6.24 kB
JavaScript
;
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;