Working tree changes 2024-07-26 00:00
All checks were successful
Test project build / Build (push) Successful in 1m3s

This commit is contained in:
Bot 2024-07-26 00:00:02 +03:00 committed by multimote
parent a33dc79fa1
commit 16324d5f5f
8 changed files with 120 additions and 88 deletions

View File

@ -33,5 +33,6 @@
"tslib": "^2.6.3", "tslib": "^2.6.3",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.3.1" "vite": "^5.3.1"
} },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@ -61,14 +61,9 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
this.dispatchTypedEvent("rawpacketreceived", new RawPacketReceivedEvent(target.value!)); this.dispatchTypedEvent("rawpacketreceived", new RawPacketReceivedEvent(target.value!));
this.dispatchTypedEvent("packetreceived", new PacketReceivedEvent(packet)); this.dispatchTypedEvent("packetreceived", new PacketReceivedEvent(packet));
if (!(packet.getCommand() in ResponseCommandId)) { if (!(packet.command in ResponseCommandId)) {
console.warn(`Unknown response command: 0x${Utils.numberToHex(packet.getCommand())}`); console.warn(`Unknown response command: 0x${Utils.numberToHex(packet.command)}`);
} /*else { }
const data = PacketParser.parse(packet);
if (data !== undefined) {
this.dispatchTypedEvent("packetparsed", new PacketParsedEvent(data));
}
}*/
}); });
await channel.startNotifications(); await channel.startNotifications();
@ -76,11 +71,8 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
this.gattServer = gattServer; this.gattServer = gattServer;
this.channel = channel; this.channel = channel;
let connectResult = ConnectResult.Connected;
try { try {
connectResult = await this.abstraction.connectResult(); await this.initialNegotiate();
console.log({connectResult: ConnectResult[connectResult]});
await this.fetchPrinterConfig(); await this.fetchPrinterConfig();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -88,7 +80,7 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
const result: ConnectionInfo = { const result: ConnectionInfo = {
deviceName: device.name, deviceName: device.name,
result: connectResult result: this.printerConfig.connectResult ?? ConnectResult.FirmwareErrors
}; };
this.dispatchTypedEvent("connect", new ConnectEvent(result)); this.dispatchTypedEvent("connect", new ConnectEvent(result));
@ -116,7 +108,7 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
return this.mutex.runExclusive(async () => { return this.mutex.runExclusive(async () => {
await this.sendPacket(packet, true); await this.sendPacket(packet, true);
if (packet.isOneWay()) { if (packet.oneWay) {
return new NiimbotPacket(-1, []); // or undefined is better? return new NiimbotPacket(-1, []); // or undefined is better?
} }
@ -127,8 +119,8 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
const listener = (evt: PacketReceivedEvent) => { const listener = (evt: PacketReceivedEvent) => {
if ( if (
packet.getValidResponseIds().length === 0 || packet.validResponseIds.length === 0 ||
packet.getValidResponseIds().includes(evt.packet.getCommand()) packet.validResponseIds.includes(evt.packet.command)
) { ) {
clearTimeout(timeout); clearTimeout(timeout);
this.removeEventListener("packetreceived", listener); this.removeEventListener("packetreceived", listener);
@ -139,7 +131,7 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
timeout = setTimeout(() => { timeout = setTimeout(() => {
this.removeEventListener("packetreceived", listener); this.removeEventListener("packetreceived", listener);
throw new Error( throw new Error(
`Timeout waiting response (waited for ${Utils.bufToHex(packet.getValidResponseIds(), ", ")})` `Timeout waiting response (waited for ${Utils.bufToHex(packet.validResponseIds, ", ")})`
); );
}, timeoutMs ?? 1000); }, timeoutMs ?? 1000);

View File

@ -5,10 +5,12 @@ import { Abstraction } from "../packets/abstraction";
export type ConnectionInfo = { export type ConnectionInfo = {
deviceName?: string; deviceName?: string;
result: ConnectResult result: ConnectResult;
}; };
export interface PrinterConfig { export interface PrinterConfig {
connectResult?: ConnectResult;
protocolVersion?: number;
model?: PrinterModelId; model?: PrinterModelId;
serial?: string; serial?: string;
mac?: string; mac?: string;
@ -52,8 +54,21 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
await this.sendRaw(packet.toBytes(), force); await this.sendRaw(packet.toBytes(), force);
} }
/** Send "connect" packet and fetch the protocol version */
protected async initialNegotiate(): Promise<void> {
const cfg = this.printerConfig;
cfg.connectResult = await this.abstraction.connectResult();
cfg.protocolVersion = 0;
if (cfg.connectResult === ConnectResult.ConnectedNew) {
cfg.protocolVersion = 1;
} else if (cfg.connectResult === ConnectResult.ConnectedV3) {
const statusData = await this.abstraction.getPrinterStatusData();
cfg.protocolVersion = statusData.protocolVersion;
}
}
public async fetchPrinterConfig(): Promise<PrinterConfig> { public async fetchPrinterConfig(): Promise<PrinterConfig> {
this.printerConfig = {};
// console.log(await this.abstraction.getPrinterStatusData()); // console.log(await this.abstraction.getPrinterStatusData());
this.printerConfig.model = await this.abstraction.getPrinterModel(); this.printerConfig.model = await this.abstraction.getPrinterModel();
this.printerConfig.serial = await this.abstraction.getPrinterSerialNumber(); this.printerConfig.serial = await this.abstraction.getPrinterSerialNumber();

View File

@ -1,5 +1,5 @@
import { ConnectionInfo, NiimbotAbstractClient } from "."; import { ConnectionInfo, NiimbotAbstractClient } from ".";
import { NiimbotPacket } from "../packets"; import { ConnectResult, NiimbotPacket } from "../packets";
import { Utils } from "../utils"; import { Utils } from "../utils";
import { ConnectEvent, DisconnectEvent, RawPacketSentEvent } from "./events"; import { ConnectEvent, DisconnectEvent, RawPacketSentEvent } from "./events";
@ -28,8 +28,16 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
this.writer = _port.writable.getWriter(); this.writer = _port.writable.getWriter();
const info = _port.getInfo(); const info = _port.getInfo();
try {
await this.initialNegotiate();
await this.fetchPrinterConfig();
} catch (e) {
console.error(e);
}
const result: ConnectionInfo = { const result: ConnectionInfo = {
deviceName: `Serial (VID:${info.usbVendorId?.toString(16)} PID:${info.usbProductId?.toString(16)})`, deviceName: `Serial (VID:${info.usbVendorId?.toString(16)} PID:${info.usbProductId?.toString(16)})`,
result: this.printerConfig.connectResult ?? ConnectResult.FirmwareErrors
}; };
this.dispatchTypedEvent("connect", new ConnectEvent(result)); this.dispatchTypedEvent("connect", new ConnectEvent(result));

View File

@ -87,25 +87,25 @@ export class Abstraction {
public async getPrintStatus(): Promise<PrintStatus> { public async getPrintStatus(): Promise<PrintStatus> {
const packet = await this.send(PacketGenerator.printStatus()); const packet = await this.send(PacketGenerator.printStatus());
if (packet.getCommand() === ResponseCommandId.In_PrintError) { if (packet.command === ResponseCommandId.In_PrintError) {
Validators.u8ArrayLengthEquals(packet.getData(), 1); Validators.u8ArrayLengthEquals(packet.data, 1);
throw new PrintError( throw new PrintError(
`Print error (${ResponseCommandId[packet.getCommand()]} packet received)`, `Print error (${ResponseCommandId[packet.command]} packet received)`,
packet.getData()[0] packet.data[0]
); );
} }
Validators.u8ArrayLengthAtLeast(packet.getData(), 4); // can be 8, 10, but ignore it for now Validators.u8ArrayLengthAtLeast(packet.data, 4); // can be 8, 10, but ignore it for now
const r = new SequentialDataReader(packet.getData()); const r = new SequentialDataReader(packet.data);
const page = r.readI16(); const page = r.readI16();
const pagePrintProgress = r.readI8(); const pagePrintProgress = r.readI8();
const pageFeedProgress = r.readI8(); const pageFeedProgress = r.readI8();
if (packet.getData().length === 10) { if (packet.dataLength === 10) {
r.skip(2); r.skip(2);
const error = r.readI8(); const error = r.readI8();
throw new PrintError(`Print error (${ResponseCommandId[packet.getCommand()]} packet flag)`, error); throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet flag)`, error);
} }
return { page, pagePrintProgress, pageFeedProgress }; return { page, pagePrintProgress, pageFeedProgress };
@ -113,8 +113,8 @@ export class Abstraction {
public async connectResult(): Promise<ConnectResult> { public async connectResult(): Promise<ConnectResult> {
const packet = await this.send(PacketGenerator.connect()); const packet = await this.send(PacketGenerator.connect());
Validators.u8ArrayLengthEquals(packet.getData(), 1); Validators.u8ArrayLengthAtLeast(packet.data, 1);
return packet.getData()[0] as ConnectResult; return packet.data[0] as ConnectResult;
} }
public async getPrinterStatusData(): Promise<PrinterStatusData> { public async getPrinterStatusData(): Promise<PrinterStatusData> {
@ -122,10 +122,10 @@ export class Abstraction {
const packet = await this.send(PacketGenerator.getPrinterStatusData()); const packet = await this.send(PacketGenerator.getPrinterStatusData());
let supportColor = 0; let supportColor = 0;
if (packet.getData().length > 12) { if (packet.dataLength > 12) {
supportColor = packet.getData()[10]; supportColor = packet.data[10];
let n = packet.getData()[11] * 100 + packet.getData()[12]; let n = packet.data[11] * 100 + packet.data[12];
if (n >= 204 && n < 300) { if (n >= 204 && n < 300) {
protocolVersion = 3; protocolVersion = 3;
} }
@ -142,9 +142,9 @@ export class Abstraction {
public async getPrinterModel(): Promise<PrinterModelId> { public async getPrinterModel(): Promise<PrinterModelId> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterModelId)); const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterModelId));
Validators.u8ArrayLengthEquals(packet.getData(), 2); Validators.u8ArrayLengthEquals(packet.data, 2);
const id = Utils.bytesToI16(packet.getData()); const id = Utils.bytesToI16(packet.data);
if (id in PrinterModelId) { if (id in PrinterModelId) {
return id as PrinterModelId; return id as PrinterModelId;
@ -166,11 +166,11 @@ export class Abstraction {
consumablesType: LabelType.Invalid, consumablesType: LabelType.Invalid,
}; };
if (packet.getData().length === 1) { if (packet.dataLength === 1) {
return info; return info;
} }
const r = new SequentialDataReader(packet.getData()); const r = new SequentialDataReader(packet.data);
info.tagPresent = true; info.tagPresent = true;
info.uuid = Utils.bufToHex(r.readBytes(8), ""); info.uuid = Utils.bufToHex(r.readBytes(8), "");
info.barCode = r.readVString(); info.barCode = r.readVString();
@ -194,8 +194,8 @@ export class Abstraction {
}; };
// originally expected packet length is bound to model id, but we make it more robust and simple // originally expected packet length is bound to model id, but we make it more robust and simple
const len = packet.getData().length; const len = packet.dataLength;
const r = new SequentialDataReader(packet.getData()); const r = new SequentialDataReader(packet.data);
if (len === 10) { if (len === 10) {
// d110 // d110
@ -235,38 +235,38 @@ export class Abstraction {
public async getBatteryChargeLevel(): Promise<BatteryChargeLevel> { public async getBatteryChargeLevel(): Promise<BatteryChargeLevel> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.BatteryChargeLevel)); const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.BatteryChargeLevel));
Validators.u8ArrayLengthEquals(packet.getData(), 1); Validators.u8ArrayLengthEquals(packet.data, 1);
return packet.getData()[0] as BatteryChargeLevel; return packet.data[0] as BatteryChargeLevel;
} }
public async getAutoShutDownTime(): Promise<AutoShutdownTime> { public async getAutoShutDownTime(): Promise<AutoShutdownTime> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.AutoShutdownTime)); const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.AutoShutdownTime));
Validators.u8ArrayLengthEquals(packet.getData(), 1); Validators.u8ArrayLengthEquals(packet.data, 1);
return packet.getData()[0] as AutoShutdownTime; return packet.data[0] as AutoShutdownTime;
} }
public async getLabelType(): Promise<LabelType> { public async getLabelType(): Promise<LabelType> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.LabelType)); const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.LabelType));
Validators.u8ArrayLengthEquals(packet.getData(), 1); Validators.u8ArrayLengthEquals(packet.data, 1);
return packet.getData()[0] as LabelType; return packet.data[0] as LabelType;
} }
public async getPrinterSerialNumber(): Promise<string> { public async getPrinterSerialNumber(): Promise<string> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.SerialNumber)); const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.SerialNumber));
Validators.u8ArrayLengthAtLeast(packet.getData(), 1); Validators.u8ArrayLengthAtLeast(packet.data, 1);
return Utils.u8ArrayToString(packet.getData()); return Utils.u8ArrayToString(packet.data);
} }
public async getPrinterBluetoothMacAddress(): Promise<string> { public async getPrinterBluetoothMacAddress(): Promise<string> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.BluetoothAddress)); const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.BluetoothAddress));
Validators.u8ArrayLengthAtLeast(packet.getData(), 1); Validators.u8ArrayLengthAtLeast(packet.data, 1);
return Utils.bufToHex(packet.getData().reverse(), ":"); return Utils.bufToHex(packet.data.reverse(), ":");
} }
public async isSoundEnabled(soundType: SoundSettingsItemType): Promise<boolean> { public async isSoundEnabled(soundType: SoundSettingsItemType): Promise<boolean> {
const packet = await this.send(PacketGenerator.getSoundSettings(soundType)); const packet = await this.send(PacketGenerator.getSoundSettings(soundType));
Validators.u8ArrayLengthEquals(packet.getData(), 3); Validators.u8ArrayLengthEquals(packet.data, 3);
const value = !!packet.getData()[2]; const value = !!packet.data[2];
return value; return value;
} }

View File

@ -13,6 +13,7 @@ export enum RequestCommandId {
PrintEmptyRow = 0x84, // -124 PrintEmptyRow = 0x84, // -124
PrintEnd = 0xf3, PrintEnd = 0xf3,
PrinterInfo = 0x40, // See PrinterInfoType PrinterInfo = 0x40, // See PrinterInfoType
PrinterConfig = 0xaf,
PrinterStatusData = 0xa5, PrinterStatusData = 0xa5,
PrintQuantity = 0x15, PrintQuantity = 0x15,
PrintStart = 0x01, PrintStart = 0x01,
@ -27,7 +28,7 @@ export enum RequestCommandId {
SetPageSize = 0x13, // 2, 4 or 6 bytes SetPageSize = 0x13, // 2, 4 or 6 bytes
SoundSettings = 0x58, SoundSettings = 0x58,
Unknown1 = 0x0b, // some info request (niimbot app), 01 long 02 short Unknown1 = 0x0b, // some info request (niimbot app), 01 long 02 short
WriteRFID = 0x70, WriteRFID = 0x70, // same as GetVolumeLevel???
} }
export enum ResponseCommandId { export enum ResponseCommandId {
@ -42,6 +43,7 @@ export enum ResponseCommandId {
In_PrintClear = 0x30, In_PrintClear = 0x30,
In_PrintEmptyRows = 0xd3, In_PrintEmptyRows = 0xd3,
In_PrintEnd = 0xf4, In_PrintEnd = 0xf4,
In_PrinterConfig = 0xbf,
In_PrinterInfoAutoShutDownTime = 0x47, In_PrinterInfoAutoShutDownTime = 0x47,
In_PrinterInfoBluetoothAddress = 0x4d, In_PrinterInfoBluetoothAddress = 0x4d,
In_PrinterInfoDensity = 0x41, In_PrinterInfoDensity = 0x41,
@ -65,7 +67,6 @@ export enum ResponseCommandId {
In_SetPageSize = 0x14, In_SetPageSize = 0x14,
In_SoundSettings = 0x68, In_SoundSettings = 0x68,
In_Unknown1 = 0xe4, In_Unknown1 = 0xe4,
In_Unknown2 = 0xc2,
} }
export enum PrinterInfoType { export enum PrinterInfoType {

View File

@ -5,45 +5,60 @@ export class NiimbotPacket {
public static readonly HEAD = new Uint8Array([0x55, 0x55]); public static readonly HEAD = new Uint8Array([0x55, 0x55]);
public static readonly TAIL = new Uint8Array([0xaa, 0xaa]); public static readonly TAIL = new Uint8Array([0xaa, 0xaa]);
private commandId: RequestCommandId; private _command: RequestCommandId;
private data: Uint8Array; private _data: Uint8Array;
private validResponseIds: ResponseCommandId[]; private _validResponseIds: ResponseCommandId[];
/** There can be no response after this request. */ /** There can be no response after this request. */
private oneWay: boolean; private _oneWay: boolean;
constructor(commandId: number, data: Uint8Array | number[], validResponseIds: ResponseCommandId[] = []) { constructor(command: number, data: Uint8Array | number[], validResponseIds: ResponseCommandId[] = []) {
this.commandId = commandId; this._command = command;
this.data = data instanceof Uint8Array ? data : new Uint8Array(data); this._data = data instanceof Uint8Array ? data : new Uint8Array(data);
this.validResponseIds = validResponseIds; this._validResponseIds = validResponseIds;
this.oneWay = false; this._oneWay = false;
} }
public setNoResponse(): void { /** Data length (header, command, dataLen, checksum, tail are excluded). */
this.oneWay = true; public get dataLength(): number {
return this._data.length;
}
public get length(): number {
return (
NiimbotPacket.HEAD.length + // head
1 + // cmd
1 + // dataLength
this.dataLength +
1 + // checksum
NiimbotPacket.TAIL.length
);
} }
public isOneWay(): boolean { public set oneWay(value: boolean) {
return this.oneWay; this._oneWay = value;
} }
public getValidResponseIds(): ResponseCommandId[] { public get oneWay(): boolean {
return this.validResponseIds; return this._oneWay;
} }
public getCommand(): number { public get validResponseIds(): ResponseCommandId[] {
return this.commandId; return this._validResponseIds;
} }
public getData(): Uint8Array { public get command(): number {
return this.data; return this._command;
} }
public checksum(): number { public get data(): Uint8Array {
return this._data;
}
public get checksum(): number {
let checksum = 0; let checksum = 0;
checksum ^= this.commandId; checksum ^= this._command;
checksum ^= this.data.length; checksum ^= this._data.length;
this.data.forEach((i: number) => (checksum ^= i)); this._data.forEach((i: number) => (checksum ^= i));
return checksum; return checksum;
} }
@ -53,7 +68,7 @@ export class NiimbotPacket {
NiimbotPacket.HEAD.length + // head NiimbotPacket.HEAD.length + // head
1 + // cmd 1 + // cmd
1 + // dataLength 1 + // dataLength
this.data.length + this._data.length +
1 + // checksum 1 + // checksum
NiimbotPacket.TAIL.length NiimbotPacket.TAIL.length
); );
@ -65,21 +80,21 @@ export class NiimbotPacket {
arr.set(NiimbotPacket.HEAD, pos); arr.set(NiimbotPacket.HEAD, pos);
pos += NiimbotPacket.HEAD.length; pos += NiimbotPacket.HEAD.length;
arr[pos] = this.commandId; arr[pos] = this._command;
pos += 1; pos += 1;
arr[pos] = this.data.length; arr[pos] = this._data.length;
pos += 1; pos += 1;
arr.set(this.data, pos); arr.set(this._data, pos);
pos += this.data.length; pos += this._data.length;
arr[pos] = this.checksum(); arr[pos] = this.checksum;
pos += 1; pos += 1;
arr.set(NiimbotPacket.TAIL, pos); arr.set(NiimbotPacket.TAIL, pos);
if(this.commandId === RequestCommandId.Connect) { if (this._command === RequestCommandId.Connect) {
// const newArr = new Uint8Array(arr.length + 1); // const newArr = new Uint8Array(arr.length + 1);
// newArr[0] = 3; // newArr[0] = 3;
// newArr.set(arr, 1); // newArr.set(arr, 1);
@ -119,7 +134,7 @@ export class NiimbotPacket {
const checksum: number = buf.getUint8(4 + dataLen); const checksum: number = buf.getUint8(4 + dataLen);
const packet = new NiimbotPacket(cmd, data); const packet = new NiimbotPacket(cmd, data);
if (packet.checksum() !== checksum) { if (packet.checksum !== checksum) {
throw new Error("Invalid packet checksum"); throw new Error("Invalid packet checksum");
} }

View File

@ -229,7 +229,7 @@ export class PacketGenerator {
// https://github.com/ayufan/niimprint-web/blob/main/cmds.js#L215 // https://github.com/ayufan/niimprint-web/blob/main/cmds.js#L215
public static printEmptySpace(pos: number, repeats: number): NiimbotPacket { public static printEmptySpace(pos: number, repeats: number): NiimbotPacket {
const packet = new NiimbotPacket(RequestCommandId.PrintEmptyRow, [...Utils.u16ToBytes(pos), repeats]); const packet = new NiimbotPacket(RequestCommandId.PrintEmptyRow, [...Utils.u16ToBytes(pos), repeats]);
packet.setNoResponse(); packet.oneWay = true;
return packet; return packet;
} }
@ -247,7 +247,7 @@ export class PacketGenerator {
repeats, repeats,
...data, ...data,
]); ]);
packet.setNoResponse(); packet.oneWay = true;
return packet; return packet;
} }
@ -268,7 +268,7 @@ export class PacketGenerator {
...indexes, ...indexes,
]); ]);
packet.setNoResponse(); packet.oneWay = true;
return packet; return packet;
} }