import { AutoShutdownTime, BatteryChargeLevel, ConnectResult, HeartbeatType, LabelType, NiimbotPacket, PacketGenerator, PrinterInfoType, PrintOptions, ResponseCommandId, SoundSettingsItemType, SoundSettingsType, EncodedImage, NiimbotAbstractClient, Utils, Validators, ProtocolVersion, } from ".."; import { SequentialDataReader } from "./data_reader"; export class PrintError extends Error { public readonly reasonId: number; constructor(message: string, reasonId: number) { super(message); this.reasonId = reasonId; } } export interface PrintStatus { /** 0 – n */ page: number; /** 0 – 100 */ pagePrintProgress: number; /** 0 – 100 */ pageFeedProgress: number; } export interface RfidInfo { tagPresent: boolean; uuid: string; barCode: string; serialNumber: string; allPaper: number; usedPaper: number; consumablesType: LabelType; } /** closingState inverted on some printers */ export interface HeartbeatData { paperState: number; rfidReadState: number; lidClosed: boolean; powerLevel: BatteryChargeLevel; } export interface SoundSettings { category: SoundSettingsType; item: SoundSettingsItemType; value: boolean; } export interface PrinterStatusData { supportColor: number; protocolVersion: number; } /** Not sure for name. */ export class Abstraction { private readonly DEFAULT_TIMEOUT: number = 1_000; private client: NiimbotAbstractClient; private timeout: number = this.DEFAULT_TIMEOUT; constructor(client: NiimbotAbstractClient) { this.client = client; } public getTimeout(): number { return this.timeout; } public setTimeout(value: number) { this.timeout = value; } public setDefaultTimeout() { this.timeout = this.DEFAULT_TIMEOUT; } /** Send packet and wait for response */ private async send(packet: NiimbotPacket): Promise<NiimbotPacket> { return this.client.sendPacketWaitResponse(packet, this.timeout); } public async getPrintStatus(): Promise<PrintStatus> { const packet = await this.send(PacketGenerator.printStatus()); if (packet.command === ResponseCommandId.In_PrintError) { Validators.u8ArrayLengthEquals(packet.data, 1); throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet received)`, packet.data[0]); } Validators.u8ArrayLengthAtLeast(packet.data, 4); // can be 8, 10, but ignore it for now const r = new SequentialDataReader(packet.data); const page = r.readI16(); const pagePrintProgress = r.readI8(); const pageFeedProgress = r.readI8(); if (packet.dataLength === 10) { r.skip(2); const error = r.readI8(); if (error !== 0) { throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet flag)`, error); } } return { page, pagePrintProgress, pageFeedProgress }; } public async connectResult(): Promise<ConnectResult> { const packet = await this.send(PacketGenerator.connect()); Validators.u8ArrayLengthAtLeast(packet.data, 1); return packet.data[0] as ConnectResult; } public async getPrinterStatusData(): Promise<PrinterStatusData> { let protocolVersion = 0; const packet = await this.send(PacketGenerator.getPrinterStatusData()); let supportColor = 0; if (packet.dataLength > 12) { supportColor = packet.data[10]; const n = packet.data[11] * 100 + packet.data[12]; if (n >= 204 && n < 300) { protocolVersion = 3; } if (n >= 301) { protocolVersion = 4; } } return { supportColor, protocolVersion, }; } public async getPrinterModel(): Promise<number> { const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterModelId)); Validators.u8ArrayLengthEquals(packet.data, 2); return Utils.bytesToI16(packet.data); } /** Read paper nfc tag info */ public async rfidInfo(): Promise<RfidInfo> { const packet = await this.send(PacketGenerator.rfidInfo()); const info: RfidInfo = { tagPresent: false, uuid: "", barCode: "", serialNumber: "", allPaper: -1, usedPaper: -1, consumablesType: LabelType.Invalid, }; if (packet.dataLength === 1) { return info; } const r = new SequentialDataReader(packet.data); info.tagPresent = true; info.uuid = Utils.bufToHex(r.readBytes(8), ""); info.barCode = r.readVString(); info.serialNumber = r.readVString(); info.allPaper = r.readI16(); info.usedPaper = r.readI16(); info.consumablesType = r.readI8() as LabelType; r.end(); return info; } public async heartbeat(): Promise<HeartbeatData> { const packet = await this.send(PacketGenerator.heartbeat(HeartbeatType.Advanced1)); const info: HeartbeatData = { paperState: -1, rfidReadState: -1, lidClosed: false, powerLevel: BatteryChargeLevel.Charge0, }; // originally expected packet length is bound to model id, but we make it more robust and simple const len = packet.dataLength; const r = new SequentialDataReader(packet.data); if (len === 10) { // d110 r.skip(8); info.lidClosed = r.readBool(); info.powerLevel = r.readI8(); } else if (len === 20) { r.skip(18); info.paperState = r.readI8(); info.rfidReadState = r.readI8(); } else if (len === 19) { r.skip(15); info.lidClosed = r.readBool(); info.powerLevel = r.readI8(); info.paperState = r.readI8(); info.rfidReadState = r.readI8(); } else if (len === 13) { // b1 r.skip(9); info.lidClosed = r.readBool(); info.powerLevel = r.readI8(); info.paperState = r.readI8(); info.rfidReadState = r.readI8(); } else { throw new Error("Invalid heartbeat length"); } r.end(); const model: number | undefined = this.client.getPrinterInfo().model_id; if (model !== undefined && ![512, 514, 513, 2304, 1792, 3584, 5120, 2560, 3840, 4352, 272].includes(model)) { info.lidClosed = !info.lidClosed; } return info; } public async getBatteryChargeLevel(): Promise<BatteryChargeLevel> { const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.BatteryChargeLevel)); Validators.u8ArrayLengthEquals(packet.data, 1); return packet.data[0] as BatteryChargeLevel; } public async getAutoShutDownTime(): Promise<AutoShutdownTime> { const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.AutoShutdownTime)); Validators.u8ArrayLengthEquals(packet.data, 1); return packet.data[0] as AutoShutdownTime; } public async setAutoShutDownTime(time: AutoShutdownTime): Promise<void> { await this.send(PacketGenerator.setAutoShutDownTime(time)); } public async getLabelType(): Promise<LabelType> { const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.LabelType)); Validators.u8ArrayLengthEquals(packet.data, 1); return packet.data[0] as LabelType; } public async getPrinterSerialNumber(): Promise<string> { const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.SerialNumber)); Validators.u8ArrayLengthAtLeast(packet.data, 1); return Utils.u8ArrayToString(packet.data); } public async getPrinterBluetoothMacAddress(): Promise<string> { const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.BluetoothAddress)); Validators.u8ArrayLengthAtLeast(packet.data, 1); return Utils.bufToHex(packet.data.reverse(), ":"); } public async isSoundEnabled(soundType: SoundSettingsItemType): Promise<boolean> { const packet = await this.send(PacketGenerator.getSoundSettings(soundType)); Validators.u8ArrayLengthEquals(packet.data, 3); const value = !!packet.data[2]; return value; } public async setSoundEnabled(soundType: SoundSettingsItemType, value: boolean): Promise<void> { await this.send(PacketGenerator.setSoundSettings(soundType, value)); } public async print( protoVersion: ProtocolVersion, image: EncodedImage, options?: PrintOptions, timeout?: number ): Promise<void> { this.setTimeout(timeout ?? 10_000); const packets: NiimbotPacket[] = PacketGenerator.generatePrintSequence(protoVersion, image, options); try { for (const element of packets) { await this.send(element); } } finally { this.setDefaultTimeout(); } } public async printEnd(): Promise<void> { await this.send(PacketGenerator.printEnd()); } }