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> </div>
<!-- svelte-ignore missing-declaration --> <!-- 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> <style>
.niim { .niim {
@ -38,6 +45,7 @@
.blue { .blue {
color: #0b7eff; color: #0b7eff;
} }
.version { .version {
position: absolute; position: absolute;
bottom: 0; bottom: 0;

View File

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

View File

@ -1,17 +1,12 @@
<script lang="ts"> <script lang="ts">
import { import { PrinterModelId } from "@mmote/niimbluelib";
PacketGenerator, import { printerClient, connectedPrinterName, connectionState, initClient, heartbeatData } from "../stores";
PrinterInfoType,
HeartbeatType,
PrinterId,
type PrinterInfoPrinterCodeDecoded,
} from "@mmote/niimbluelib";
import { printerClient, connectedPrinterName, connectionState, initClient } from "../stores";
import type { ConnectionType } from "../types"; import type { ConnectionType } from "../types";
import FaIcon from "./FaIcon.svelte"; import FaIcon from "./FaIcon.svelte";
import { icon } from "@fortawesome/fontawesome-svg-core";
let connectionType: ConnectionType = "bluetooth"; let connectionType: ConnectionType = "bluetooth";
let timer: NodeJS.Timeout | undefined = undefined;
const onConnectClicked = async () => { const onConnectClicked = async () => {
initClient(connectionType); initClient(connectionType);
connectionState.set("connecting"); connectionState.set("connecting");
@ -27,31 +22,25 @@
$printerClient.disconnect(); $printerClient.disconnect();
}; };
const getRfidInfo = async () => { const getRfidInfo = async () => {
const data = await $printerClient.sendPacketWaitResponseDecoded(PacketGenerator.rfidInfo()); const data = await $printerClient.abstraction.rfidInfo();
alert(JSON.stringify(data, null, 2)); 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 () => { const startHeartbeat = async () => {
timer = setInterval(async () => { // timer = setInterval(async () => {
const data = await $printerClient.sendPacketWaitResponseDecoded( // const data = await $printerClient.abstraction.heartbeat();
PacketGenerator.heartbeat(HeartbeatType.Unknown1) // console.log(data);
); // }, 1000);
}, 1000); $printerClient.startHeartbeat();
}; };
const test2 = async () => { const stopHeartbeat = async () => {
clearInterval(timer); // clearInterval(timer);
$printerClient.stopHeartbeat();
}; };
const test3 = async () => { const test3 = async () => {
const info = (await $printerClient.sendPacketWaitResponseDecoded( const id = await $printerClient.abstraction.getPrinterModel();
PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterCode) alert(`Printer model: ${PrinterModelId[id]}`);
)) as PrinterInfoPrinterCodeDecoded;
const id: string | undefined = PrinterId[info.code];
alert(`Printer model: ${id ?? "Unknown"}`);
}; };
</script> </script>
@ -61,9 +50,12 @@
><FaIcon icon="gear" /> ><FaIcon icon="gear" />
</button> </button>
<div class="dropdown-menu p-1"> <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={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={startHeartbeat}>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={stopHeartbeat}>stop heartbeat (test)</button>
<button class="btn btn-sm btn-primary" on:click={test3}>Guess printer model</button> <button class="btn btn-sm btn-primary" on:click={test3}>Guess printer model</button>
</div> </div>
<span class="input-group-text">{$connectedPrinterName}</span> <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 type { ConnectionState, ConnectionType } from "./types";
import { import {
ConnectEvent, ConnectEvent,
HeartbeatEvent,
NiimbotBluetoothClient, NiimbotBluetoothClient,
NiimbotSerialClient, NiimbotSerialClient,
PacketParsedEvent, // PacketParsedEvent,
RawPacketReceivedEvent, RawPacketReceivedEvent,
RawPacketSentEvent, RawPacketSentEvent,
Utils, Utils,
type HeartbeatData,
type NiimbotAbstractClient, type NiimbotAbstractClient,
} from "@mmote/niimbluelib"; } from "@mmote/niimbluelib";
export const connectionState = writable<ConnectionState>("disconnected"); export const connectionState = writable<ConnectionState>("disconnected");
export const connectedPrinterName = writable<string>(""); export const connectedPrinterName = writable<string>("");
export const printerClient = writable<NiimbotAbstractClient>(); export const printerClient = writable<NiimbotAbstractClient>();
export const heartbeatData = writable<HeartbeatData>();
export const initClient = (connectionType: ConnectionType) => { export const initClient = (connectionType: ConnectionType) => {
printerClient.update((prevClient: NiimbotAbstractClient) => { printerClient.update((prevClient: NiimbotAbstractClient) => {
@ -43,9 +46,9 @@ export const initClient = (connectionType: ConnectionType) => {
console.log(`<< ${Utils.bufToHex(e.data)}`); console.log(`<< ${Utils.bufToHex(e.data)}`);
}); });
newClient.addEventListener("packetparsed", (e: PacketParsedEvent) => { // newClient.addEventListener("packetparsed", (e: PacketParsedEvent) => {
console.log(e.packet); // console.log(e.packet);
}); // });
newClient.addEventListener("connect", (e: ConnectEvent) => { newClient.addEventListener("connect", (e: ConnectEvent) => {
console.log("onConnect"); console.log("onConnect");
@ -59,6 +62,10 @@ export const initClient = (connectionType: ConnectionType) => {
connectionState.set("disconnected"); connectionState.set("disconnected");
connectedPrinterName.set(""); connectedPrinterName.set("");
}); });
newClient.addEventListener("heartbeat", (e: HeartbeatEvent) => {
heartbeatData.set(e.data);
});
} }
return newClient; return newClient;

View File

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

View File

@ -1,15 +1,34 @@
import { ParsedPacket, NiimbotPacket, PacketParser } from "../packets"; import { NiimbotPacket, PrinterModelId } from "../packets";
import { TypedEventTarget } from 'typescript-event-target'; import { TypedEventTarget } from "typescript-event-target";
import { ClientEventMap, ConnectionInfo } from "./events"; 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> { 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 */ /** Connect to printer port */
public abstract connect(): Promise<ConnectionInfo>; public abstract connect(): Promise<ConnectionInfo>;
/** Disconnect from printer port */ /** Disconnect from printer port */
public abstract disconnect(): Promise<void>; public abstract disconnect(): Promise<void>;
public abstract isConnected(): boolean;
/** /**
* Send packet and wait for response. * Send packet and wait for response.
* If packet.responsePacketCommandId is defined, it will wait for packet with this command id. * 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); await this.sendRaw(packet.toBytes(), force);
} }
public async sendPacketWaitResponseDecoded(packet: NiimbotPacket, timeoutMs?: number): Promise<ParsedPacket> { // todo: delete
const response: NiimbotPacket = await this.sendPacketWaitResponse(packet, timeoutMs); // public async sendPacketWaitResponseParsed(packet: NiimbotPacket, timeoutMs?: number): Promise<ParsedPacket> {
// const response: NiimbotPacket = await this.sendPacketWaitResponse(packet, timeoutMs);
// todo: prevent double parsing without duplicating sendPacketWaitResponse // // todo: prevent double parsing without duplicating sendPacketWaitResponse
const data = PacketParser.parse(response); // const data = PacketParser.parse(response);
if (!data) { // if (!data) {
throw new Error("Decoder for this packet is not implemented or packet is invalid"); // throw new Error("Decoder for this packet is not implemented or packet is invalid");
} // }
return data; // 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; this.writer = undefined;
} }
public isConnected(): boolean {
throw this.port !== undefined && this.writer !== undefined;
}
public async sendPacketWaitResponse(packet: NiimbotPacket, timeoutMs: number = 1000): Promise<NiimbotPacket> { public async sendPacketWaitResponse(packet: NiimbotPacket, timeoutMs: number = 1000): Promise<NiimbotPacket> {
if (this.port === undefined) { if (this.port === undefined) {
throw new Error("Port is closed"); throw new Error("Port is closed");

View File

@ -147,6 +147,13 @@ export class PacketGenerator {
return new NiimbotPacket(RequestCommandId.PrintQuantity, [h, l]); 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. * 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 "./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 { LabelType, NiimbotPacket, ResponseCommandId, SoundSettingsItemType, SoundSettingsType } from "..";
import { Utils, Validators } from "../.."; import { Utils, Validators } from "../..";
import { SequentialDataReader } from "../parser/data_reader"; import { SequentialDataReader } from "../parser/data_reader";
export interface ParsedPacket { export interface ParsedPacket {
commandId: ResponseCommandId; commandId: ResponseCommandId;
} }
export interface PrintErrorDecoded extends ParsedPacket {
/**
* 8 - no paper?
*/
errorType: number;
}
export interface SettingsInDecoded extends ParsedPacket { export interface SettingsInDecoded extends ParsedPacket {
category: SoundSettingsType; category: SoundSettingsType;
item: SoundSettingsItemType; item: SoundSettingsItemType;
@ -31,9 +42,20 @@ export interface RfidInfoDecoded extends ParsedPacket {
consumablesType: LabelType; consumablesType: LabelType;
} }
export interface PrinterInfoPrinterCodeDecoded extends ParsedPacket { export interface PrinterInfoPrinterModelIdDecoded extends ParsedPacket {
/** See {@link PrinterId} */ /** 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 */ /** closingState inverted on some printers */
@ -45,15 +67,15 @@ export interface HeartbeatDecoded extends ParsedPacket {
} }
export class PacketParser { export class PacketParser {
private static decodePrinterInfoSerialPacket(packet: NiimbotPacket): PrinterInfoSerialDecoded { private static decodePrinterInfoSerial(packet: NiimbotPacket): PrinterInfoSerialDecoded {
return { return {
commandId: packet.getCommand(), commandId: packet.getCommand(),
serialNumber: Utils.u8ArrayToString(packet.getData()), serialNumber: Utils.u8ArrayToString(packet.getData()),
}; };
} }
private static decodeSettingsPacket(packet: NiimbotPacket): SettingsInDecoded { private static decodeSettings(packet: NiimbotPacket): SettingsInDecoded {
Validators.u8ArrayLengthEqual(packet.getData(), 3); Validators.u8ArrayLengthEquals(packet.getData(), 3);
return { return {
commandId: packet.getCommand(), commandId: packet.getCommand(),
@ -63,8 +85,8 @@ export class PacketParser {
}; };
} }
private static decodePrinterInfoLabelTypePacket(packet: NiimbotPacket): PrinterInfoLabelTypeDecoded { private static decodePrinterInfoLabelType(packet: NiimbotPacket): PrinterInfoLabelTypeDecoded {
Validators.u8ArrayLengthEqual(packet.getData(), 1); Validators.u8ArrayLengthEquals(packet.getData(), 1);
return { return {
commandId: packet.getCommand(), commandId: packet.getCommand(),
@ -72,12 +94,12 @@ export class PacketParser {
}; };
} }
private static decodePrinterInfoPrinterCodePacket(packet: NiimbotPacket): PrinterInfoPrinterCodeDecoded { private static decodePrinterInfoPrinterModelId(packet: NiimbotPacket): PrinterInfoPrinterModelIdDecoded {
Validators.u8ArrayLengthEqual(packet.getData(), 2); Validators.u8ArrayLengthEquals(packet.getData(), 2);
return { return {
commandId: packet.getCommand(), commandId: packet.getCommand(),
code: Utils.bytesToI16(packet.getData()), model: Utils.bytesToI16(packet.getData()),
}; };
} }
@ -169,22 +191,136 @@ export class PacketParser {
return info; 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 { public static parse(packet: NiimbotPacket): ParsedPacket | undefined {
const command: ResponseCommandId = packet.getCommand(); const command: ResponseCommandId = packet.getCommand();
switch (command) { switch (command) {
case ResponseCommandId.In_SoundSettings: case ResponseCommandId.In_SoundSettings:
return this.decodeSettingsPacket(packet); return this.decodeSettings(packet);
case ResponseCommandId.In_PrinterInfoSerialNumber: case ResponseCommandId.In_PrinterInfoSerialNumber:
return this.decodePrinterInfoSerialPacket(packet); return this.decodePrinterInfoSerial(packet);
case ResponseCommandId.In_PrinterInfoLabelType: case ResponseCommandId.In_PrinterInfoLabelType:
return this.decodePrinterInfoLabelTypePacket(packet); return this.decodePrinterInfoLabelType(packet);
case ResponseCommandId.In_RfidInfo: case ResponseCommandId.In_RfidInfo:
return this.decodeRfidInfo(packet); return this.decodeRfidInfo(packet);
case ResponseCommandId.In_PrinterInfoPrinterCode: case ResponseCommandId.In_PrinterInfoPrinterCode:
return this.decodePrinterInfoPrinterCodePacket(packet); return this.decodePrinterInfoPrinterModelId(packet);
case ResponseCommandId.In_Heartbeat1: case ResponseCommandId.In_Heartbeat1:
return this.decodeHeartbeat(packet); return this.decodeHeartbeat(packet);
case ResponseCommandId.In_PrintError:
return this.decodePrintError(packet);
case ResponseCommandId.In_PrintStatus:
return this.decodePrintStatus(packet);
} }
return undefined; return undefined;
} }

View File

@ -51,7 +51,8 @@ export enum ResponseCommandId {
In_PrinterInfoSoftWareVersion = 0x49, In_PrinterInfoSoftWareVersion = 0x49,
In_PrinterInfoUnknown1 = 0x4f, In_PrinterInfoUnknown1 = 0x4f,
IN_PrinterStatusData = 0xb5, 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_PrintQuantity = 0x16,
In_PrintStart = 0x02, In_PrintStart = 0x02,
In_RfidInfo = 0x1b, In_RfidInfo = 0x1b,
@ -72,7 +73,7 @@ export enum PrinterInfoType {
Language = 6, Language = 6,
AutoShutDownTime = 7, AutoShutDownTime = 7,
/** See {@link PrinterId} */ /** See {@link PrinterId} */
PrinterCode = 8, PrinterModelId = 8,
SoftWareVersion = 9, SoftWareVersion = 9,
Electricity = 10, Electricity = 10,
SerialNumber = 11, SerialNumber = 11,
@ -127,7 +128,7 @@ export enum PowerLevel {
} }
/** Generated from android app (assets/flutter_assets/assets/config/printerList.json) */ /** Generated from android app (assets/flutter_assets/assets/config/printerList.json) */
export enum PrinterId { export enum PrinterModelId {
UNKNOWN = 0, UNKNOWN = 0,
T6 = 51715, T6 = 51715,
TP2M_H = 4609, TP2M_H = 4609,

View File

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