Working tree changes 2024-07-22 00:00

This commit is contained in:
Bot 2024-07-22 00:00:01 +03:00 committed by multimote
parent 4e5250fc06
commit 9344bb5997
14 changed files with 613 additions and 105 deletions

View File

@ -29,7 +29,14 @@
</div>
<!-- svelte-ignore missing-declaration -->
<div class="version text-secondary">v{__APP_VERSION__} built at {__BUILD_DATE__}</div>
<div class="version text-end text-secondary">
<div>
v{__APP_VERSION__} built at {__BUILD_DATE__}
</div>
<div>
<a class="text-secondary" href="https://gitee.mmote.ru/MultiMote/niimblue-nightly">code (temporary)</a>
</div>
</div>
<style>
.niim {
@ -38,6 +45,7 @@
.blue {
color: #0b7eff;
}
.version {
position: absolute;
bottom: 0;

View File

@ -4,7 +4,17 @@
import Modal from "bootstrap/js/dist/modal";
import { connectionState, printerClient } from "../stores";
import { copyImageData, threshold, atkinson } from "../post_process";
import { type EncodedImage, ImageEncoder, Utils, LabelType, PacketGenerator, PrintSequenceVariant } from "@mmote/niimbluelib";
import {
type EncodedImage,
ImageEncoder,
Utils,
LabelType,
PacketGenerator,
PrintSequenceVariant,
type PrintStatusDecoded,
ResponseCommandId,
PrintError,
} from "@mmote/niimbluelib";
import type { LabelProps } from "../types";
import FaIcon from "./FaIcon.svelte";
@ -17,6 +27,7 @@
let previewCanvas: HTMLCanvasElement;
let modal: Modal;
let sendProgress: number = 0;
let printProgress: number = 0; // todo: more progress data
let density: number = 3;
let quantity: number = 1;
let printed: boolean = false;
@ -25,28 +36,48 @@
let imgData: ImageData;
let imgContext: CanvasRenderingContext2D;
let protoVariant: PrintSequenceVariant = PrintSequenceVariant.V1;
let statusTimer: NodeJS.Timeout | undefined = undefined;
let printError: boolean = false;
const disconnected = derived(connectionState, ($connectionState) => $connectionState !== "connected");
const onPrint = async () => {
const encoded: EncodedImage = ImageEncoder.encodeCanvas(previewCanvas, labelProps.startPos);
const packets = PacketGenerator.generatePrintSequence(protoVariant, encoded, { quantity, density });
for (let i = 0; i < packets.length; i++) {
sendProgress = Math.round(((i + 1) / packets.length) * 100);
// console.log(Utils.bufToHex(packets[i].toBytes()))
await $printerClient.sendPacketWaitResponse(packets[i]);
const cancelPrint = async () => {
clearInterval(statusTimer);
// await Utils.sleep(100);
// console.log(Utils.bufToHex(packets[i].toBytes()))
}
printed = true;
};
const onEndPrint = async () => {
if (!$disconnected && printed) {
await $printerClient.sendPacket(PacketGenerator.printEnd());
printed = false;
}
printed = false;
printProgress = 0;
};
const onPrint = async () => {
printError = false;
const encoded: EncodedImage = ImageEncoder.encodeCanvas(previewCanvas, labelProps.startPos);
const packets = PacketGenerator.generatePrintSequence(protoVariant, encoded, { quantity, density });
for (let i = 0; i < packets.length; i++) {
sendProgress = Math.round(((i + 1) / packets.length) * 100);
await $printerClient.sendPacketWaitResponse(packets[i], 10_000);
}
statusTimer = setInterval(async () => {
try {
const status = await $printerClient.abstraction.getPrintStatus();
printProgress = status.pagePrintProgress;
if (status.page === quantity && status.pagePrintProgress === 100 && status.pageFeedProgress === 100) {
await cancelPrint();
}
} catch (e) {
console.error(e);
await cancelPrint();
printError = true;
}
}, 100);
printed = true;
};
const updatePreview = () => {
@ -78,7 +109,7 @@
modal = new Modal(modalElement);
modal.show();
modalElement.addEventListener("hidden.bs.modal", async () => {
onEndPrint();
cancelPrint();
onClosed();
});
});
@ -117,8 +148,20 @@
</div>
</div>
{/if}
{#if printProgress != 0 && printProgress != 100}
<div>
Printing...
<div class="progress" role="progressbar">
<div class="progress-bar" style="width: {printProgress}%">{printProgress}%</div>
</div>
</div>
{/if}
</div>
{#if printError}
<div class="alert alert-danger" role="alert">Print error</div>
{/if}
<div class="modal-footer">
<div class="input-group input-group-sm">
<span class="input-group-text">Post-process</span>
@ -144,24 +187,24 @@
<input class="form-control" type="number" min="1" max="6" bind:value={density} />
</div>
<!-- <div class="input-group flex-nowrap input-group-sm">
<div class="input-group flex-nowrap input-group-sm">
<span class="input-group-text">Quantity</span>
<input class="form-control" type="number" min="1" bind:value={quantity} />
</div> -->
</div>
<div class="input-group input-group-sm">
<span class="input-group-text">Protocol variant</span>
<select class="form-select" bind:value={protoVariant}>
<option value="{PrintSequenceVariant.V1}">V1 - D110</option>
<option value="{PrintSequenceVariant.V2}">V2 - B1</option>
<option value={PrintSequenceVariant.V1}>V1 - D110</option>
<option value={PrintSequenceVariant.V2}>V2 - B1</option>
</select>
</div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
{#if printed}
<button type="button" class="btn btn-primary" disabled={$disconnected} on:click={onEndPrint}>
End print
<button type="button" class="btn btn-primary" disabled={$disconnected} on:click={cancelPrint}>
Cancel print
</button>
{/if}

View File

@ -1,17 +1,12 @@
<script lang="ts">
import {
PacketGenerator,
PrinterInfoType,
HeartbeatType,
PrinterId,
type PrinterInfoPrinterCodeDecoded,
} from "@mmote/niimbluelib";
import { printerClient, connectedPrinterName, connectionState, initClient } from "../stores";
import { PrinterModelId } from "@mmote/niimbluelib";
import { printerClient, connectedPrinterName, connectionState, initClient, heartbeatData } from "../stores";
import type { ConnectionType } from "../types";
import FaIcon from "./FaIcon.svelte";
import { icon } from "@fortawesome/fontawesome-svg-core";
let connectionType: ConnectionType = "bluetooth";
let timer: NodeJS.Timeout | undefined = undefined;
const onConnectClicked = async () => {
initClient(connectionType);
connectionState.set("connecting");
@ -27,31 +22,25 @@
$printerClient.disconnect();
};
const getRfidInfo = async () => {
const data = await $printerClient.sendPacketWaitResponseDecoded(PacketGenerator.rfidInfo());
const data = await $printerClient.abstraction.rfidInfo();
alert(JSON.stringify(data, null, 2));
// const data = Uint8Array.of(1,1,1,1);
// await $printerClient.sendPacketWaitResponse(PacketGenerator.writeRfid(data), 1000);
// await $printerClient.sendPacketWaitResponse(PacketGenerator.rfidInfo(), 1000);
};
const test = async () => {
timer = setInterval(async () => {
const data = await $printerClient.sendPacketWaitResponseDecoded(
PacketGenerator.heartbeat(HeartbeatType.Unknown1)
);
}, 1000);
const startHeartbeat = async () => {
// timer = setInterval(async () => {
// const data = await $printerClient.abstraction.heartbeat();
// console.log(data);
// }, 1000);
$printerClient.startHeartbeat();
};
const test2 = async () => {
clearInterval(timer);
const stopHeartbeat = async () => {
// clearInterval(timer);
$printerClient.stopHeartbeat();
};
const test3 = async () => {
const info = (await $printerClient.sendPacketWaitResponseDecoded(
PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterCode)
)) as PrinterInfoPrinterCodeDecoded;
const id: string | undefined = PrinterId[info.code];
alert(`Printer model: ${id ?? "Unknown"}`);
const id = await $printerClient.abstraction.getPrinterModel();
alert(`Printer model: ${PrinterModelId[id]}`);
};
</script>
@ -61,9 +50,12 @@
><FaIcon icon="gear" />
</button>
<div class="dropdown-menu p-1">
<div>
<FaIcon icon="battery-empty"/> {($heartbeatData?.powerLevel ?? 0) * 25}%
</div>
<button class="btn btn-sm btn-primary" on:click={getRfidInfo}>RfidInfo</button>
<button class="btn btn-sm btn-primary" on:click={test}>start heartbeat (test)</button>
<button class="btn btn-sm btn-primary" on:click={test2}>stop heartbeat (test)</button>
<button class="btn btn-sm btn-primary" on:click={startHeartbeat}>start heartbeat (test)</button>
<button class="btn btn-sm btn-primary" on:click={stopHeartbeat}>stop heartbeat (test)</button>
<button class="btn btn-sm btn-primary" on:click={test3}>Guess printer model</button>
</div>
<span class="input-group-text">{$connectedPrinterName}</span>

View File

@ -2,18 +2,21 @@ import { writable } from "svelte/store";
import type { ConnectionState, ConnectionType } from "./types";
import {
ConnectEvent,
HeartbeatEvent,
NiimbotBluetoothClient,
NiimbotSerialClient,
PacketParsedEvent,
// PacketParsedEvent,
RawPacketReceivedEvent,
RawPacketSentEvent,
Utils,
type HeartbeatData,
type NiimbotAbstractClient,
} from "@mmote/niimbluelib";
export const connectionState = writable<ConnectionState>("disconnected");
export const connectedPrinterName = writable<string>("");
export const printerClient = writable<NiimbotAbstractClient>();
export const heartbeatData = writable<HeartbeatData>();
export const initClient = (connectionType: ConnectionType) => {
printerClient.update((prevClient: NiimbotAbstractClient) => {
@ -43,9 +46,9 @@ export const initClient = (connectionType: ConnectionType) => {
console.log(`<< ${Utils.bufToHex(e.data)}`);
});
newClient.addEventListener("packetparsed", (e: PacketParsedEvent) => {
console.log(e.packet);
});
// newClient.addEventListener("packetparsed", (e: PacketParsedEvent) => {
// console.log(e.packet);
// });
newClient.addEventListener("connect", (e: ConnectEvent) => {
console.log("onConnect");
@ -59,6 +62,10 @@ export const initClient = (connectionType: ConnectionType) => {
connectionState.set("disconnected");
connectedPrinterName.set("");
});
newClient.addEventListener("heartbeat", (e: HeartbeatEvent) => {
heartbeatData.set(e.data);
});
}
return newClient;

View File

@ -5,7 +5,6 @@ import { Utils } from "../utils";
import {
ConnectEvent,
DisconnectEvent,
PacketParsedEvent,
PacketReceivedEvent,
RawPacketReceivedEvent,
RawPacketSentEvent,
@ -41,6 +40,8 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
const disconnectListener = async () => {
this.gattServer = undefined;
this.channel = undefined;
this.config = {};
this.stopHeartbeat();
this.dispatchTypedEvent("disconnect", new DisconnectEvent());
device.removeEventListener("gattserverdisconnected", disconnectListener);
};
@ -62,12 +63,12 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
if (!(packet.getCommand() in ResponseCommandId)) {
console.warn(`Unknown response command: 0x${Utils.numberToHex(packet.getCommand())}`);
} else {
} /*else {
const data = PacketParser.parse(packet);
if (data !== undefined) {
this.dispatchTypedEvent("packetparsed", new PacketParsedEvent(data));
}
}
}*/
});
await channel.startNotifications();
@ -79,15 +80,28 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
deviceName: device.name,
};
try {
const data = await this.fetchPrinterConfig();
console.log(data);
} catch (e) {
console.error(e);
}
this.dispatchTypedEvent("connect", new ConnectEvent(result));
return result;
}
public isConnected(): boolean {
return this.gattServer !== undefined && this.channel !== undefined;
}
public async disconnect() {
this.stopHeartbeat();
this.gattServer?.disconnect();
this.gattServer = undefined;
this.channel = undefined;
this.config = {};
}
/**
@ -120,7 +134,9 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
timeout = setTimeout(() => {
this.removeEventListener("packetreceived", listener);
throw new Error("Timeout waiting response");
throw new Error(
`Timeout waiting response (waited for ${Utils.bufToHex(packet.getValidResponseIds(), ", ")})`
);
}, timeoutMs ?? 1000);
this.addEventListener("packetreceived", listener);

View File

@ -1,8 +1,6 @@
import { NiimbotPacket, ParsedPacket } from "../packets";
import { ConnectionInfo } from ".";
import { HeartbeatData, NiimbotPacket, ParsedPacket } from "../packets";
export type ConnectionInfo = {
deviceName?: string;
};
/**
*
@ -27,13 +25,13 @@ export class DisconnectEvent extends Event {
/**
*
*/
export class PacketParsedEvent extends Event {
packet: ParsedPacket;
constructor(packet: ParsedPacket) {
super("packetparsed");
this.packet = packet;
}
}
// export class PacketParsedEvent extends Event {
// packet: ParsedPacket;
// constructor(packet: ParsedPacket) {
// super("packetparsed");
// this.packet = packet;
// }
// }
/**
*
@ -68,11 +66,23 @@ export class RawPacketReceivedEvent extends Event {
}
}
/**
*
*/
export class HeartbeatEvent extends Event {
data: HeartbeatData;
constructor(data: HeartbeatData) {
super("heartbeat");
this.data = data;
}
}
export interface ClientEventMap {
connect: ConnectEvent;
disconnect: DisconnectEvent;
rawpacketsent: RawPacketSentEvent;
rawpacketreceived: RawPacketReceivedEvent;
packetreceived: PacketReceivedEvent;
packetparsed: PacketParsedEvent;
heartbeat: HeartbeatEvent;
// packetparsed: PacketParsedEvent;
}

View File

@ -1,15 +1,34 @@
import { ParsedPacket, NiimbotPacket, PacketParser } from "../packets";
import { TypedEventTarget } from 'typescript-event-target';
import { ClientEventMap, ConnectionInfo } from "./events";
import { NiimbotPacket, PrinterModelId } from "../packets";
import { TypedEventTarget } from "typescript-event-target";
import { ClientEventMap, HeartbeatEvent } from "./events";
import { Abstraction } from "../packets/parser/abstraction";
export type ConnectionInfo = {
deviceName?: string;
};
export interface PrinterConfig {
model?: PrinterModelId;
}
export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEventMap> {
public readonly abstraction: Abstraction;
protected config: PrinterConfig = {};
private heartbeatTimer?: NodeJS.Timeout;
constructor() {
super();
this.abstraction = new Abstraction(this);
}
/** Connect to printer port */
public abstract connect(): Promise<ConnectionInfo>;
/** Disconnect from printer port */
public abstract disconnect(): Promise<void>;
public abstract isConnected(): boolean;
/**
* Send packet and wait for response.
* If packet.responsePacketCommandId is defined, it will wait for packet with this command id.
@ -28,15 +47,47 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
await this.sendRaw(packet.toBytes(), force);
}
public async sendPacketWaitResponseDecoded(packet: NiimbotPacket, timeoutMs?: number): Promise<ParsedPacket> {
const response: NiimbotPacket = await this.sendPacketWaitResponse(packet, timeoutMs);
// todo: delete
// public async sendPacketWaitResponseParsed(packet: NiimbotPacket, timeoutMs?: number): Promise<ParsedPacket> {
// const response: NiimbotPacket = await this.sendPacketWaitResponse(packet, timeoutMs);
// todo: prevent double parsing without duplicating sendPacketWaitResponse
const data = PacketParser.parse(response);
if (!data) {
throw new Error("Decoder for this packet is not implemented or packet is invalid");
}
return data;
// // todo: prevent double parsing without duplicating sendPacketWaitResponse
// const data = PacketParser.parse(response);
// if (!data) {
// throw new Error("Decoder for this packet is not implemented or packet is invalid");
// }
// return data;
// }
public async fetchPrinterConfig(): Promise<PrinterConfig> {
const props: PrinterConfig = {};
props.model = await this.abstraction.getPrinterModel();
return props;
}
public getConfig(): PrinterConfig {
return this.config;
}
/**
* Starts the heartbeat timer, "heartbeat" is emitted after packet received.
*
* @param interval Heartbeat interval, default is 1000ms
*/
public startHeartbeat(intervalMs: number = 1000): void {
this.heartbeatTimer = setInterval(async () => {
const data = await this.abstraction.heartbeat();
this.dispatchTypedEvent("heartbeat", new HeartbeatEvent(data));
}, intervalMs);
}
public stopHeartbeat(): void {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = undefined;
}
public isHeartbeatStarted(): boolean {
return this.heartbeatTimer === undefined;
}
}

View File

@ -50,6 +50,10 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
this.writer = undefined;
}
public isConnected(): boolean {
throw this.port !== undefined && this.writer !== undefined;
}
public async sendPacketWaitResponse(packet: NiimbotPacket, timeoutMs: number = 1000): Promise<NiimbotPacket> {
if (this.port === undefined) {
throw new Error("Port is closed");

View File

@ -147,6 +147,13 @@ export class PacketGenerator {
return new NiimbotPacket(RequestCommandId.PrintQuantity, [h, l]);
}
public static printStatus(): NiimbotPacket {
return new NiimbotPacket(RequestCommandId.PrintStatus, [1], [
ResponseCommandId.In_PrintStatus,
ResponseCommandId.In_PrintError
]);
}
/**
* B1 behavior: after {@link pageEnd} paper stops at printhead position, on {@link printEnd} paper moved further.
*

View File

@ -0,0 +1,227 @@
import {
HeartbeatType as HeartbeatType,
LabelType,
NiimbotPacket,
PacketGenerator,
PrinterInfoType,
PrinterModelId,
ResponseCommandId,
SoundSettingsItemType,
SoundSettingsType,
} from "..";
import { NiimbotAbstractClient, Utils, Validators } from "../..";
import { SequentialDataReader } from "../parser/data_reader";
// ---------------------------------------------------------
// experiments
// export abstract class ParsedPacket2 {
// public readonly commandId: ResponseCommandId;
// constructor(commandId: ResponseCommandId) {
// this.commandId = commandId;
// }
// public abstract parse(packet: NiimbotPacket): ParsedPacket2;
// }
// export class PrintErrorDecoded2 extends ParsedPacket2 {
// public errorType: number;
// constructor() {
// super(ResponseCommandId.In_PrintError);
// this.errorType = 0;
// }
// public parse(packet: NiimbotPacket): PrintErrorDecoded2 {
// Validators.u8ArrayLengthEqual(packet.getData(), 1);
// const p = new PrintErrorDecoded2();
// p.errorType = packet.getData()[0];
// return p;
// }
// }
// ---------------------------------------------------------
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;
closingState: number;
powerLevel: number;
}
/** Experimental class for retrieving data. Not sure for name. */
export class Abstraction {
private client: NiimbotAbstractClient;
private timeout: number = 1000;
constructor(client: NiimbotAbstractClient) {
this.client = client;
}
public getTimeout(): number {
return this.timeout;
}
public setTimeout(value: number) {
this.timeout = value;
}
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.getCommand() === ResponseCommandId.In_PrintError) {
Validators.u8ArrayLengthEquals(packet.getData(), 1);
throw new PrintError(
`Print error (${ResponseCommandId[packet.getCommand()]} packet received)`,
packet.getData()[0]
);
}
Validators.u8ArrayLengthAtLeast(packet.getData(), 4); // can be 8, 10, but ignore it for now
const r = new SequentialDataReader(packet.getData());
const page = r.readI16();
const pagePrintProgress = r.readI8();
const pageFeedProgress = r.readI8();
if (packet.getData().length === 10) {
r.skip(2);
const error = r.readI8();
throw new PrintError(`Print error (${ResponseCommandId[packet.getCommand()]} packet flag)`, error);
}
return { page, pagePrintProgress, pageFeedProgress };
}
public async getPrinterModel(): Promise<PrinterModelId> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterModelId));
Validators.u8ArrayLengthEquals(packet.getData(), 2);
const id = Utils.bytesToI16(packet.getData());
if (id in PrinterModelId) {
return id as PrinterModelId;
}
return PrinterModelId.UNKNOWN;
}
/** 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.getData().length === 1) {
return info;
}
const r = new SequentialDataReader(packet.getData());
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.Unknown1));
const info: HeartbeatData = {
paperState: -1,
rfidReadState: -1,
closingState: -1,
powerLevel: -1,
};
// originally expected packet length is calculated from model id, but we make it simple
const len = packet.getData().length;
const r = new SequentialDataReader(packet.getData());
if (len === 17 - 7) {
// d110
// n3 = n8 + 9;
// DataCheck.parseClosingState(callback, byArray[n3], n2);
// DataCheck.parsePowerLevel(callback, byArray[n8 + 10]);
// DataCheck.parseRfidReadState(callback, byArray[n3], n2); ??????
r.skip(8);
info.closingState = r.readI8();
info.powerLevel = r.readI8();
} else if (len === 27 - 7) {
// 19 parsePaperState
// 20 parseRfidReadState
r.skip(18);
info.paperState = r.readI8();
info.rfidReadState = r.readI8();
} else if (len === 26 - 7) {
// 16 parseClosingState
// 17 parsePowerLevel
// 18 parsePaperState
// 19 parseRfidReadState
r.skip(15);
info.closingState = r.readI8();
info.powerLevel = r.readI8();
info.paperState = r.readI8();
info.rfidReadState = r.readI8();
} else if (len === 20 - 7) {
// b1
r.skip(9);
info.closingState = r.readI8();
info.powerLevel = r.readI8();
info.paperState = r.readI8();
info.rfidReadState = r.readI8();
// 10 parseClosingState
// 11 parsePowerLevel
// 12 parsePaperState
// 13 parseRfidReadState
} else {
throw new Error("Invalid heartbeat length");
}
r.end();
return info;
}
}

View File

@ -1,2 +1,3 @@
export * from "./data_reader"
export * from "./parsed_packets"
export * from "./parsed_packets"
export * from "./abstraction"

View File

@ -1,11 +1,22 @@
// todo: migrate to Abstraction class
import { LabelType, NiimbotPacket, ResponseCommandId, SoundSettingsItemType, SoundSettingsType } from "..";
import { Utils, Validators } from "../..";
import { SequentialDataReader } from "../parser/data_reader";
export interface ParsedPacket {
commandId: ResponseCommandId;
}
export interface PrintErrorDecoded extends ParsedPacket {
/**
* 8 - no paper?
*/
errorType: number;
}
export interface SettingsInDecoded extends ParsedPacket {
category: SoundSettingsType;
item: SoundSettingsItemType;
@ -31,9 +42,20 @@ export interface RfidInfoDecoded extends ParsedPacket {
consumablesType: LabelType;
}
export interface PrinterInfoPrinterCodeDecoded extends ParsedPacket {
export interface PrinterInfoPrinterModelIdDecoded extends ParsedPacket {
/** See {@link PrinterId} */
code: number;
model: number;
}
export interface PrintStatusDecoded extends ParsedPacket {
/** 0 n */
page: number;
/** 0 100 */
pagePrintProgress: number;
/** 0 100 */
pageFeedProgress: number;
error?: number
}
/** closingState inverted on some printers */
@ -45,15 +67,15 @@ export interface HeartbeatDecoded extends ParsedPacket {
}
export class PacketParser {
private static decodePrinterInfoSerialPacket(packet: NiimbotPacket): PrinterInfoSerialDecoded {
private static decodePrinterInfoSerial(packet: NiimbotPacket): PrinterInfoSerialDecoded {
return {
commandId: packet.getCommand(),
serialNumber: Utils.u8ArrayToString(packet.getData()),
};
}
private static decodeSettingsPacket(packet: NiimbotPacket): SettingsInDecoded {
Validators.u8ArrayLengthEqual(packet.getData(), 3);
private static decodeSettings(packet: NiimbotPacket): SettingsInDecoded {
Validators.u8ArrayLengthEquals(packet.getData(), 3);
return {
commandId: packet.getCommand(),
@ -63,8 +85,8 @@ export class PacketParser {
};
}
private static decodePrinterInfoLabelTypePacket(packet: NiimbotPacket): PrinterInfoLabelTypeDecoded {
Validators.u8ArrayLengthEqual(packet.getData(), 1);
private static decodePrinterInfoLabelType(packet: NiimbotPacket): PrinterInfoLabelTypeDecoded {
Validators.u8ArrayLengthEquals(packet.getData(), 1);
return {
commandId: packet.getCommand(),
@ -72,12 +94,12 @@ export class PacketParser {
};
}
private static decodePrinterInfoPrinterCodePacket(packet: NiimbotPacket): PrinterInfoPrinterCodeDecoded {
Validators.u8ArrayLengthEqual(packet.getData(), 2);
private static decodePrinterInfoPrinterModelId(packet: NiimbotPacket): PrinterInfoPrinterModelIdDecoded {
Validators.u8ArrayLengthEquals(packet.getData(), 2);
return {
commandId: packet.getCommand(),
code: Utils.bytesToI16(packet.getData()),
model: Utils.bytesToI16(packet.getData()),
};
}
@ -169,22 +191,136 @@ export class PacketParser {
return info;
}
/*
d110, 3 pages
5555 b3 04 00000000 b7aaaa
5555 b3 04 00000000 b7aaaa
5555 b3 04 00000000 b7aaaa
5555 b3 04 00001000 a7aaaa
5555 b3 04 00002100 96aaaa
5555 b3 04 00002100 96aaaa
5555 b3 04 00004200 f5aaaa
5555 b3 04 00005300 e4aaaa
5555 b3 04 00006400 d3aaaa
5555 b3 04 00006419 caaaaa
5555 b3 04 00006432 e1aaaa
5555 b3 04 0000644d 9eaaaa
5555 b3 04 00010000 b6aaaa
5555 b3 04 00010000 b6aaaa
5555 b3 04 00010000 b6aaaa
5555 b3 04 00012100 97aaaa
5555 b3 04 00012100 97aaaa
5555 b3 04 00012100 97aaaa
5555 b3 04 00014200 f4aaaa
5555 b3 04 00015300 e5aaaa
5555 b3 04 00016404 d6aaaa
5555 b3 04 00016420 f2aaaa
5555 b3 04 0001643f edaaaa
5555 b3 04 00016460 b2aaaa
5555 b3 04 00020000 b5aaaa
5555 b3 04 00020000 b5aaaa
5555 b3 04 00020000 b5aaaa
5555 b3 04 00022100 94aaaa
5555 b3 04 00022100 94aaaa
5555 b3 04 00023200 87aaaa
5555 b3 04 00025300 e6aaaa
5555 b3 04 00025300 e6aaaa
5555 b3 04 00026422 f3aaaa
5555 b3 04 00026446 97aaaa
5555 b3 04 00036464 b4aaaa
b3, 3 pages
5555 b3 0a 0000 0000031f00000000 a5aaaa
5555 b3 0a 0000 0000031a00000000 a0aaaa
5555 b3 0a 0000 0000031900010000 a2aaaa
5555 b3 0a 0000 0000031900010000 a2aaaa
5555 b3 0a 0000 0000031900010000 a2aaaa
5555 b3 0a 0000 0c00031900010000 aeaaaa
5555 b3 0a 0000 1d00031900010000 bfaaaa
5555 b3 0a 0000 2e00031900010000 8caaaa
5555 b3 0a 0000 4200031900010000 e0aaaa
5555 b3 0a 0000 5600031900010000 f4aaaa
5555 b3 0a 0000 642c031900010000 eaaaaa
5555 b3 0a 0001 0a00031900010000 a9aaaa
5555 b3 0a 0001 1e00031900010000 bdaaaa
5555 b3 0a 0001 2f00031900010000 8caaaa
5555 b3 0a 0001 4600031900010000 e5aaaa
5555 b3 0a 0001 5500031900010000 f6aaaa
5555 b3 0a 0001 6400031900010000 c7aaaa
5555 b3 0a 0001 6403031900010000 c4aaaa
5555 b3 0a 0001 640c031900010000 cbaaaa
5555 b3 0a 0001 6435031900010000 f2aaaa
5555 b3 0a 0001 6463031900010000 a4aaaa
5555 b3 0a 0002 1200031900010000 b2aaaa
5555 b3 0a 0002 2600031b00010000 84aaaa
5555 b3 0a 0002 3800031b00010000 9aaaaa
5555 b3 0a 0002 4b00031d00010000 efaaaa
5555 b3 0a 0002 5d00031e00010000 faaaaa
5555 b3 0a 0002 6401031e00010000 c2aaaa
5555 b3 0a 0002 6405031e00010000 c6aaaa
5555 b3 0a 0002 642f031e00010000 ecaaaa
5555 b3 0a 0002 6451031e00010000 92aaaa
5555 b3 0a 0003 6464031e00010000 a6aaaa
*/
private static decodePrintStatus(packet: NiimbotPacket): PrintStatusDecoded {
Validators.u8ArrayLengthAtLeast(packet.getData(), 4); // can be 8, 10, but ignore it for now
const result: PrintStatusDecoded = {
commandId: packet.getCommand(),
page: -1,
pagePrintProgress: -1,
pageFeedProgress: -1,
};
const r = new SequentialDataReader(packet.getData());
result.page = r.readI16();
result.pagePrintProgress = r.readI8();
result.pageFeedProgress = r.readI8();
if(packet.getData().length === 10) {
r.skip(2)
result.error = r.readI8();
}
return result;
}
private static decodePrintError(packet: NiimbotPacket): PrintErrorDecoded {
Validators.u8ArrayLengthEquals(packet.getData(), 1);
return {
commandId: packet.getCommand(),
errorType: packet.getData()[0]
};
}
public static parse(packet: NiimbotPacket): ParsedPacket | undefined {
const command: ResponseCommandId = packet.getCommand();
switch (command) {
case ResponseCommandId.In_SoundSettings:
return this.decodeSettingsPacket(packet);
return this.decodeSettings(packet);
case ResponseCommandId.In_PrinterInfoSerialNumber:
return this.decodePrinterInfoSerialPacket(packet);
return this.decodePrinterInfoSerial(packet);
case ResponseCommandId.In_PrinterInfoLabelType:
return this.decodePrinterInfoLabelTypePacket(packet);
return this.decodePrinterInfoLabelType(packet);
case ResponseCommandId.In_RfidInfo:
return this.decodeRfidInfo(packet);
case ResponseCommandId.In_PrinterInfoPrinterCode:
return this.decodePrinterInfoPrinterCodePacket(packet);
return this.decodePrinterInfoPrinterModelId(packet);
case ResponseCommandId.In_Heartbeat1:
return this.decodeHeartbeat(packet);
case ResponseCommandId.In_PrintError:
return this.decodePrintError(packet);
case ResponseCommandId.In_PrintStatus:
return this.decodePrintStatus(packet);
}
return undefined;
}

View File

@ -51,7 +51,8 @@ export enum ResponseCommandId {
In_PrinterInfoSoftWareVersion = 0x49,
In_PrinterInfoUnknown1 = 0x4f,
IN_PrinterStatusData = 0xb5,
In_PrintParamsError = 0xdb, // For example, sent on SetPageSize when page print is not started
In_PrintStatus = 0xb3,
In_PrintError = 0xdb, // For example, sent on SetPageSize when page print is not started
In_PrintQuantity = 0x16,
In_PrintStart = 0x02,
In_RfidInfo = 0x1b,
@ -72,7 +73,7 @@ export enum PrinterInfoType {
Language = 6,
AutoShutDownTime = 7,
/** See {@link PrinterId} */
PrinterCode = 8,
PrinterModelId = 8,
SoftWareVersion = 9,
Electricity = 10,
SerialNumber = 11,
@ -127,7 +128,7 @@ export enum PowerLevel {
}
/** Generated from android app (assets/flutter_assets/assets/config/printerList.json) */
export enum PrinterId {
export enum PrinterModelId {
UNKNOWN = 0,
T6 = 51715,
TP2M_H = 4609,

View File

@ -4,7 +4,7 @@ export class Utils {
return hex.length === 1 ? `0${hex}` : hex;
}
public static bufToHex(buf: DataView | Uint8Array, separator: string = " "): string {
public static bufToHex(buf: DataView | Uint8Array | number[], separator: string = " "): string {
const arr: number[] = buf instanceof DataView ? this.dataViewToNumberArray(buf) : Array.from(buf);
return arr.map(Utils.numberToHex).join(separator);
}
@ -53,7 +53,7 @@ export class Utils {
/** Big endian */
public static bytesToI16(arr: Uint8Array): number {
Validators.u8ArrayLengthEqual(arr, 2);
Validators.u8ArrayLengthEquals(arr, 2);
return arr[0] * 256 + arr[1];
}
@ -80,9 +80,14 @@ export class Validators {
throw new Error(message ?? "Arrays must be equal");
}
}
public static u8ArrayLengthEqual(a: Uint8Array, len: number, message?: string): void {
public static u8ArrayLengthEquals(a: Uint8Array, len: number, message?: string): void {
if (a.length !== len) {
throw new Error(message ?? `Array length must be ${len}`);
}
}
public static u8ArrayLengthAtLeast(a: Uint8Array, len: number, message?: string): void {
if (a.length < len) {
throw new Error(message ?? `Array length must be at least ${len}`);
}
}
}