Working tree changes 2024-07-22 00:00
This commit is contained in:
parent
4e5250fc06
commit
9344bb5997
@ -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;
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
*
|
||||
|
227
niimbluelib/src/packets/parser/abstraction.ts
Normal file
227
niimbluelib/src/packets/parser/abstraction.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
export * from "./data_reader"
|
||||
export * from "./parsed_packets"
|
||||
export * from "./parsed_packets"
|
||||
export * from "./abstraction"
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user