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

This commit is contained in:
Bot 2024-07-28 00:00:01 +03:00 committed by multimote
parent 16324d5f5f
commit 266a392401
16 changed files with 334 additions and 272 deletions

View File

@ -0,0 +1,28 @@
<script lang="ts">
import { fabric } from "fabric";
import FaIcon from "./FaIcon.svelte";
export let selectedObject: fabric.Object;
export let valueUpdated: () => void;
const putToCenterV = () => {
selectedObject.canvas?.centerObjectH(selectedObject);
selectedObject.centerV();
valueUpdated();
};
const putToCenterH = () => {
selectedObject.centerH();
valueUpdated();
};
</script>
<button class="btn btn-sm btn-secondary" on:click={putToCenterV} title="Center vertically">
<FaIcon icon="up-down" />
</button>
<button class="btn btn-sm btn-secondary" on:click={putToCenterH} title="Center horizontally">
<FaIcon icon="left-right" />
</button>
<style>
</style>

View File

@ -9,6 +9,7 @@
import FaIcon from "./FaIcon.svelte"; import FaIcon from "./FaIcon.svelte";
import PrintPreview from "./PrintPreview.svelte"; import PrintPreview from "./PrintPreview.svelte";
import TextParamsPanel from "./TextParamsControls.svelte"; import TextParamsPanel from "./TextParamsControls.svelte";
import GenericObjectParamsControls from "./GenericObjectParamsControls.svelte";
let htmlCanvas: HTMLCanvasElement; let htmlCanvas: HTMLCanvasElement;
let fabricCanvas: fabric.Canvas; let fabricCanvas: fabric.Canvas;
@ -94,9 +95,10 @@
if (name === "text") { if (name === "text") {
const obj = new fabric.IText("Text", { const obj = new fabric.IText("Text", {
...fabricObjectDefaults, ...fabricObjectDefaults,
fontFamily: "Arial", fontFamily: "Arial"
}); });
fabricCanvas.add(obj); fabricCanvas.add(obj);
obj.center();
} else if (name === "line") { } else if (name === "line") {
const obj = new fabric.Line([10, 10, 10 + defaultSize, 10], { const obj = new fabric.Line([10, 10, 10 + defaultSize, 10], {
...fabricObjectDefaults, ...fabricObjectDefaults,
@ -111,8 +113,8 @@
mt: false, mt: false,
mb: false, mb: false,
}); });
fabricCanvas.add(obj); fabricCanvas.add(obj);
obj.centerV();
} else if (name === "circle") { } else if (name === "circle") {
const obj = new fabric.Circle({ const obj = new fabric.Circle({
...fabricObjectDefaults, ...fabricObjectDefaults,
@ -122,6 +124,7 @@
strokeWidth: 3, strokeWidth: 3,
}); });
fabricCanvas.add(obj); fabricCanvas.add(obj);
obj.centerV();
} else if (name === "rectangle") { } else if (name === "rectangle") {
const obj = new fabric.Rect({ const obj = new fabric.Rect({
...fabricObjectDefaults, ...fabricObjectDefaults,
@ -132,6 +135,7 @@
strokeWidth: 3, strokeWidth: 3,
}); });
fabricCanvas.add(obj); fabricCanvas.add(obj);
obj.centerV();
} }
}; };
@ -288,8 +292,9 @@
<button class="btn btn-sm btn-danger me-1" on:click={deleteSelected}><FaIcon icon="trash" /></button> <button class="btn btn-sm btn-danger me-1" on:click={deleteSelected}><FaIcon icon="trash" /></button>
{/if} {/if}
{#if selectedCount === 1} {#if selectedObject && selectedCount === 1}
<button class="btn btn-sm btn-secondary me-1" on:click={cloneSelected}><FaIcon icon="clone" /></button> <button class="btn btn-sm btn-secondary me-1" on:click={cloneSelected}><FaIcon icon="clone" /></button>
<GenericObjectParamsControls {selectedObject} valueUpdated={() => fabricCanvas.requestRenderAll()} />
{/if} {/if}
{#if selectedObject instanceof fabric.IText} {#if selectedObject instanceof fabric.IText}

View File

@ -7,19 +7,14 @@
import { import {
type EncodedImage, type EncodedImage,
ImageEncoder, ImageEncoder,
Utils,
LabelType,
PacketGenerator, PacketGenerator,
ProtocolVariant, ProtocolVersion,
ResponseCommandId,
PrintError,
} from "@mmote/niimbluelib"; } from "@mmote/niimbluelib";
import type { LabelProps } from "../types"; import type { LabelProps } from "../types";
import FaIcon from "./FaIcon.svelte"; import FaIcon from "./FaIcon.svelte";
export let onClosed: () => void; export let onClosed: () => void;
export let labelProps: LabelProps; export let labelProps: LabelProps;
export let imageCallback: () => string; export let imageCallback: () => string;
let modalElement: HTMLElement; let modalElement: HTMLElement;
@ -34,7 +29,7 @@
let thresholdValue: number = 140; let thresholdValue: number = 140;
let imgData: ImageData; let imgData: ImageData;
let imgContext: CanvasRenderingContext2D; let imgContext: CanvasRenderingContext2D;
let protoVariant: ProtocolVariant = ProtocolVariant.V3; let printTaskVersion: ProtocolVersion = ProtocolVersion.V3;
let statusTimer: NodeJS.Timeout | undefined = undefined; let statusTimer: NodeJS.Timeout | undefined = undefined;
let printError: boolean = false; let printError: boolean = false;
@ -54,7 +49,7 @@
const onPrint = async () => { const onPrint = async () => {
printError = false; printError = false;
const encoded: EncodedImage = ImageEncoder.encodeCanvas(previewCanvas, labelProps.startPos); const encoded: EncodedImage = ImageEncoder.encodeCanvas(previewCanvas, labelProps.startPos);
const packets = PacketGenerator.generatePrintSequence(protoVariant, encoded, { quantity, density }); const packets = PacketGenerator.generatePrintSequence(printTaskVersion, encoded, { quantity, density });
for (let i = 0; i < packets.length; i++) { for (let i = 0; i < packets.length; i++) {
sendProgress = Math.round(((i + 1) / packets.length) * 100); sendProgress = Math.round(((i + 1) / packets.length) * 100);
@ -92,15 +87,14 @@
}; };
onMount(() => { onMount(() => {
// create image from fabric canvas to work with
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
previewCanvas.width = img.width; previewCanvas.width = img.width;
previewCanvas.height = img.height; previewCanvas.height = img.height;
imgContext = previewCanvas.getContext("2d")!; imgContext = previewCanvas.getContext("2d")!;
imgContext.drawImage(img, 0, 0, img.width, img.height); imgContext.drawImage(img, 0, 0, img.width, img.height);
imgData = imgContext.getImageData(0, 0, img.width, img.height); imgData = imgContext.getImageData(0, 0, img.width, img.height);
updatePreview(); updatePreview();
}; };
img.src = imageCallback(); img.src = imageCallback();
@ -111,6 +105,12 @@
cancelPrint(); cancelPrint();
onClosed(); onClosed();
}); });
const taskVer = $printerClient?.getCapabilities().printTaskVersion;
if (taskVer !== undefined && taskVer !== ProtocolVersion.UNKNOWN) {
console.log(`Detected print task version: ${ProtocolVersion[taskVer]}`)
printTaskVersion = taskVer;
}
}); });
onDestroy(() => { onDestroy(() => {
@ -192,10 +192,13 @@
</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">Print task version</span>
<select class="form-select" bind:value={protoVariant}> <select class="form-select" bind:value={printTaskVersion}>
<option value={ProtocolVariant.V3}>V3 - D110</option> <option value={ProtocolVersion.V1} disabled>V1 - NOT IMPLEMENTED</option>
<option value={ProtocolVariant.V4}>V4 - B1</option> <option value={ProtocolVersion.V2} disabled>V2 - NOT IMPLEMENTED</option>
<option value={ProtocolVersion.V3}>V3 - D110</option>
<option value={ProtocolVersion.V4}>V4 - B1</option>
<option value={ProtocolVersion.V5} disabled>V5 - NOT IMPLEMENTED</option>
</select> </select>
</div> </div>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { PrinterModelId, type RfidInfo } from "@mmote/niimbluelib"; import { PrinterModel, type RfidInfo } from "@mmote/niimbluelib";
import { printerClient, connectedPrinterName, connectionState, initClient, heartbeatData, printerConfig } from "../stores"; import { printerClient, connectedPrinterName, connectionState, initClient, heartbeatData, printerConfig } from "../stores";
import type { ConnectionType } from "../types"; import type { ConnectionType } from "../types";
import FaIcon from "./FaIcon.svelte"; import FaIcon from "./FaIcon.svelte";
@ -36,11 +36,6 @@
// clearInterval(timer); // clearInterval(timer);
$printerClient.stopHeartbeat(); $printerClient.stopHeartbeat();
}; };
const test3 = async () => {
const id = await $printerClient.abstraction.getPrinterModel();
alert(`Printer model: ${PrinterModelId[id]}`);
};
</script> </script>
<div class="input-group flex-nowrap justify-content-end"> <div class="input-group flex-nowrap justify-content-end">
@ -49,24 +44,33 @@
><FaIcon icon="gear" /> ><FaIcon icon="gear" />
</button> </button>
<div class="dropdown-menu p-1"> <div class="dropdown-menu p-1">
<div>
Model: {$printerConfig.model === undefined ? "?" : (PrinterModel[$printerConfig.model] ?? "Unknown")}
</div>
<div> <div>
Printer info: Printer info:
<pre>{JSON.stringify($printerConfig, null, 1)}</pre> <pre>{JSON.stringify($printerConfig, null, 1)}</pre>
</div> </div>
<div>
Rfid info:
<pre>{JSON.stringify(rfidInfo || {}, null, 1)}</pre>
</div>
<div> <div>
Heartbeat data: Heartbeat data:
<pre>{JSON.stringify($heartbeatData, null, 1)}</pre> <pre>{JSON.stringify($heartbeatData, null, 1)}</pre>
</div> </div>
<div> <div>
Rfid info: Tests
<pre>{JSON.stringify(rfidInfo || {}, null, 1)}</pre>
</div> </div>
<button class="btn btn-sm btn-primary" on:click={getRfidInfo}>RfidInfo</button>
<button class="btn btn-sm btn-primary" on:click={startHeartbeat}>start heartbeat (test)</button> <button class="btn btn-sm btn-primary" on:click={getRfidInfo}>Rfid</button>
<button class="btn btn-sm btn-primary" on:click={stopHeartbeat}>stop heartbeat (test)</button> <button class="btn btn-sm btn-primary" on:click={startHeartbeat}>Heartbeat on</button>
<button class="btn btn-sm btn-primary" on:click={test3}>Guess printer model</button> <button class="btn btn-sm btn-primary" on:click={stopHeartbeat}>Heartbeat off</button>
</div> </div>
<span class="input-group-text">{$connectedPrinterName}</span> <span class="input-group-text">{$connectedPrinterName}</span>
{:else} {:else}

View File

@ -54,14 +54,17 @@
{#if selectedText} {#if selectedText}
<!-- <div class="d-flex flex-wrap gap-1"> --> <!-- <div class="d-flex flex-wrap gap-1"> -->
<button <button
title="Align text: Left"
class="btn btn-sm {selectedText.textAlign === 'left' ? 'btn-secondary' : ''}" class="btn btn-sm {selectedText.textAlign === 'left' ? 'btn-secondary' : ''}"
on:click={() => setAlign("left")}><FaIcon icon="align-left" /></button on:click={() => setAlign("left")}><FaIcon icon="align-left" /></button
> >
<button <button
title="Align text: Center"
class="btn btn-sm {selectedText.textAlign === 'center' ? 'btn-secondary' : ''}" class="btn btn-sm {selectedText.textAlign === 'center' ? 'btn-secondary' : ''}"
on:click={() => setAlign("center")}><FaIcon icon="align-center" /></button on:click={() => setAlign("center")}><FaIcon icon="align-center" /></button
> >
<button <button
title="Align text: Right"
class="btn btn-sm {selectedText.textAlign === 'right' ? 'btn-secondary' : ''}" class="btn btn-sm {selectedText.textAlign === 'right' ? 'btn-secondary' : ''}"
on:click={() => setAlign("right")}><FaIcon icon="align-right" /></button on:click={() => setAlign("right")}><FaIcon icon="align-right" /></button
> >

View File

@ -2,25 +2,12 @@ export const copyImageData = (iData: ImageData): ImageData => {
return new ImageData(new Uint8ClampedArray(iData.data), iData.width, iData.height); return new ImageData(new Uint8ClampedArray(iData.data), iData.width, iData.height);
}; };
export const convertImgDataToBlackAndWhite = (iData: ImageData, threshold = 0xff): ImageData => { // Original code is taken from https://github.com/NielsLeenheer/CanvasDither
for (let x = 0; x < iData.width; x++) {
for (let i = 0; i < iData.data.byteLength / 4; i++) {
const pos = i * 4;
const b =
iData.data[pos] >= threshold || iData.data[pos + 1] >= threshold || iData.data[pos + 2] >= threshold ? 0xff : 0;
iData.data[pos] = b;
iData.data[pos + 1] = b;
iData.data[pos + 2] = b;
iData.data[pos + 3] = 0xff;
}
}
return iData;
};
/** /**
* Change the image to blank and white using a simple threshold * Change the image to blank and white using a simple threshold
* *
*
* @param {object} image The imageData of a Canvas 2d context * @param {object} image The imageData of a Canvas 2d context
* @param {number} threshold Threshold value (0-255) * @param {number} threshold Threshold value (0-255)
* @return {object} The resulting imageData * @return {object} The resulting imageData
@ -29,7 +16,6 @@ export const convertImgDataToBlackAndWhite = (iData: ImageData, threshold = 0xff
export const threshold = (image: ImageData, threshold: number): ImageData => { export const threshold = (image: ImageData, threshold: number): ImageData => {
for (let i = 0; i < image.data.length; i += 4) { for (let i = 0; i < image.data.length; i += 4) {
const luminance = image.data[i] * 0.299 + image.data[i + 1] * 0.587 + image.data[i + 2] * 0.114; const luminance = image.data[i] * 0.299 + image.data[i + 1] * 0.587 + image.data[i + 2] * 0.114;
const value = luminance < threshold ? 0 : 255; const value = luminance < threshold ? 0 : 255;
image.data.fill(value, i, i + 3); image.data.fill(value, i, i + 3);
} }
@ -37,68 +23,11 @@ export const threshold = (image: ImageData, threshold: number): ImageData => {
return image; return image;
}; };
/**
* Change the image to blank and white using the Bayer algorithm
*
* @param {object} image The imageData of a Canvas 2d context
* @param {number} threshold Threshold value (0-255)
* @return {object} The resulting imageData
*
*/
export const bayer = (image: ImageData, threshold: number): ImageData => {
const thresholdMap = [
[15, 135, 45, 165],
[195, 75, 225, 105],
[60, 180, 30, 150],
[240, 120, 210, 90],
];
for (let i = 0; i < image.data.length; i += 4) {
const luminance = image.data[i] * 0.299 + image.data[i + 1] * 0.587 + image.data[i + 2] * 0.114;
const x = (i / 4) % image.width;
const y = Math.floor(i / 4 / image.width);
const map = Math.floor((luminance + thresholdMap[x % 4][y % 4]) / 2);
const value = map < threshold ? 0 : 255;
image.data.fill(value, i, i + 3);
}
return image;
};
/**
* Change the image to blank and white using the Floyd-Steinberg algorithm
*
* @param {object} image The imageData of a Canvas 2d context
* @return {object} The resulting imageData
*
*/
export const floydsteinberg = (image: ImageData, threshold: number): ImageData => {
const width = image.width;
const luminance = new Uint8ClampedArray(image.width * image.height);
for (let l = 0, i = 0; i < image.data.length; l++, i += 4) {
luminance[l] = image.data[i] * 0.299 + image.data[i + 1] * 0.587 + image.data[i + 2] * 0.114;
}
for (let l = 0, i = 0; i < image.data.length; l++, i += 4) {
const value = luminance[l] < threshold ? 0 : 255;
const error = Math.floor((luminance[l] - value) / 16);
image.data.fill(value, i, i + 3);
luminance[l + 1] += error * 7;
luminance[l + width - 1] += error * 3;
luminance[l + width] += error * 5;
luminance[l + width + 1] += error * 1;
}
return image;
};
/** /**
* Change the image to blank and white using the Atkinson algorithm * Change the image to blank and white using the Atkinson algorithm
* *
* @param {object} image The imageData of a Canvas 2d context * @param {object} image The imageData of a Canvas 2d context
* @param {number} threshold Threshold value (0-255)
* @return {object} The resulting imageData * @return {object} The resulting imageData
* *
*/ */
@ -125,35 +54,3 @@ export const atkinson = (image: ImageData, threshold: number): ImageData => {
return image; return image;
}; };
// https://observablehq.com/@tmcw/dithering
export const atkinson2 = (image: ImageData, threshold: number):ImageData => {
let GRAYS = 256;
let THRESHOLD = [];
for (let i = 0; i < GRAYS; i++) {
THRESHOLD.push(i < (GRAYS >> 1) ? 0 : GRAYS - 1);
}
let clone = new ImageData(new Uint8ClampedArray(image.data), image.width, image.height);
function px(x:number, y:number) {
return (x * 4) + (y * image.width * 4);
}
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
let oldPixel = clone.data[px(x, y)];
let grayNew = THRESHOLD[oldPixel];
let grayErr = (oldPixel - grayNew) >> 3;
let newPixel = oldPixel > 125 ? 255 : 0;
clone.data[px(x, y)] = clone.data[px(x, y) + 1] = clone.data[px(x, y) + 2] = newPixel;
clone.data[px(x, y)] =
clone.data[px(x, y) + 1] =
clone.data[px(x, y) + 2] = grayNew;
[[1, 0], [2, 0], [-1, 1], [0, 1], [1, 1], [0, 2]].forEach(([dx, dy]) => {
clone.data[px(x + dx, y + dy)] += grayErr;
});
}
}
return clone;
}

View File

@ -52,7 +52,7 @@ export const initClient = (connectionType: ConnectionType) => {
console.log("onConnect"); console.log("onConnect");
connectionState.set("connected"); connectionState.set("connected");
connectedPrinterName.set(e.info.deviceName ?? "unknown"); connectedPrinterName.set(e.info.deviceName ?? "unknown");
printerConfig.set(newClient.getPrinterConfig()); printerConfig.set(newClient.getPrinterInfo());
}); });
newClient.addEventListener("disconnect", () => { newClient.addEventListener("disconnect", () => {

View File

@ -40,7 +40,7 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
const disconnectListener = async () => { const disconnectListener = async () => {
this.gattServer = undefined; this.gattServer = undefined;
this.channel = undefined; this.channel = undefined;
this.printerConfig = {}; this.info = {};
this.stopHeartbeat(); this.stopHeartbeat();
this.dispatchTypedEvent("disconnect", new DisconnectEvent()); this.dispatchTypedEvent("disconnect", new DisconnectEvent());
device.removeEventListener("gattserverdisconnected", disconnectListener); device.removeEventListener("gattserverdisconnected", disconnectListener);
@ -73,14 +73,14 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
try { try {
await this.initialNegotiate(); await this.initialNegotiate();
await this.fetchPrinterConfig(); await this.fetchPrinterInfo();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
const result: ConnectionInfo = { const result: ConnectionInfo = {
deviceName: device.name, deviceName: device.name,
result: this.printerConfig.connectResult ?? ConnectResult.FirmwareErrors result: this.info.connectResult ?? ConnectResult.FirmwareErrors
}; };
this.dispatchTypedEvent("connect", new ConnectEvent(result)); this.dispatchTypedEvent("connect", new ConnectEvent(result));
@ -97,7 +97,7 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
this.gattServer?.disconnect(); this.gattServer?.disconnect();
this.gattServer = undefined; this.gattServer = undefined;
this.channel = undefined; this.channel = undefined;
this.printerConfig = {}; this.info = {};
} }
/** /**

View File

@ -1,7 +1,8 @@
import { AutoShutdownTime, BatteryChargeLevel, ConnectResult, NiimbotPacket, PrinterModelId } from "../packets"; import { AutoShutdownTime, BatteryChargeLevel, ConnectResult, NiimbotPacket } from "../packets";
import { TypedEventTarget } from "typescript-event-target"; import { TypedEventTarget } from "typescript-event-target";
import { ClientEventMap, HeartbeatEvent } from "./events"; import { ClientEventMap, HeartbeatEvent } from "./events";
import { Abstraction } from "../packets/abstraction"; import { Abstraction } from "../packets/abstraction";
import { getPrinterCapabilities, PrinterCapabilities, PrinterModel } from "../printers";
export type ConnectionInfo = { export type ConnectionInfo = {
deviceName?: string; deviceName?: string;
@ -11,7 +12,7 @@ export type ConnectionInfo = {
export interface PrinterConfig { export interface PrinterConfig {
connectResult?: ConnectResult; connectResult?: ConnectResult;
protocolVersion?: number; protocolVersion?: number;
model?: PrinterModelId; model?: PrinterModel;
serial?: string; serial?: string;
mac?: string; mac?: string;
charge?: BatteryChargeLevel; charge?: BatteryChargeLevel;
@ -20,7 +21,7 @@ export interface PrinterConfig {
export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEventMap> { export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEventMap> {
public readonly abstraction: Abstraction; public readonly abstraction: Abstraction;
protected printerConfig: PrinterConfig = {}; protected info: PrinterConfig = {};
private heartbeatTimer?: NodeJS.Timeout; private heartbeatTimer?: NodeJS.Timeout;
constructor() { constructor() {
@ -56,7 +57,7 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
/** Send "connect" packet and fetch the protocol version */ /** Send "connect" packet and fetch the protocol version */
protected async initialNegotiate(): Promise<void> { protected async initialNegotiate(): Promise<void> {
const cfg = this.printerConfig; const cfg = this.info;
cfg.connectResult = await this.abstraction.connectResult(); cfg.connectResult = await this.abstraction.connectResult();
cfg.protocolVersion = 0; cfg.protocolVersion = 0;
@ -68,18 +69,18 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
} }
} }
public async fetchPrinterConfig(): Promise<PrinterConfig> { public async fetchPrinterInfo(): Promise<PrinterConfig> {
// console.log(await this.abstraction.getPrinterStatusData()); // console.log(await this.abstraction.getPrinterStatusData());
this.printerConfig.model = await this.abstraction.getPrinterModel(); this.info.model = await this.abstraction.getPrinterModel();
this.printerConfig.serial = await this.abstraction.getPrinterSerialNumber(); this.info.serial = await this.abstraction.getPrinterSerialNumber();
this.printerConfig.mac = await this.abstraction.getPrinterBluetoothMacAddress(); this.info.mac = await this.abstraction.getPrinterBluetoothMacAddress();
this.printerConfig.charge = await this.abstraction.getBatteryChargeLevel(); this.info.charge = await this.abstraction.getBatteryChargeLevel();
this.printerConfig.autoShutdownTime = await this.abstraction.getAutoShutDownTime(); this.info.autoShutdownTime = await this.abstraction.getAutoShutDownTime();
return this.printerConfig; return this.info;
} }
public getPrinterConfig(): PrinterConfig { public getPrinterInfo(): PrinterConfig {
return this.printerConfig; return this.info;
} }
/** /**
@ -102,6 +103,11 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
public isHeartbeatStarted(): boolean { public isHeartbeatStarted(): boolean {
return this.heartbeatTimer === undefined; return this.heartbeatTimer === undefined;
} }
/** Get printer capabilities based on the printer model */
public getCapabilities(): PrinterCapabilities {
return getPrinterCapabilities(this.info.model ?? PrinterModel.UNKNOWN);
}
} }
export * from "./events"; export * from "./events";

View File

@ -30,14 +30,14 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
try { try {
await this.initialNegotiate(); await this.initialNegotiate();
await this.fetchPrinterConfig(); await this.fetchPrinterInfo();
} catch (e) { } catch (e) {
console.error(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 result: this.info.connectResult ?? ConnectResult.FirmwareErrors
}; };
this.dispatchTypedEvent("connect", new ConnectEvent(result)); this.dispatchTypedEvent("connect", new ConnectEvent(result));

View File

@ -95,7 +95,7 @@ export class ImageEncoder {
} }
/** /**
* @param data Encoded pixels (every byte is 8 pixels) * @param data Pixels encoded by {@link encodeCanvas} (byte is 8 pixels)
* @returns Array of indexes where every index stored in two bytes (big endian) * @returns Array of indexes where every index stored in two bytes (big endian)
*/ */
public static indexPixels(data: Uint8Array): Uint8Array { public static indexPixels(data: Uint8Array): Uint8Array {

View File

@ -2,3 +2,4 @@ export * from "./client";
export * from "./packets"; export * from "./packets";
export * from "./image_encoder"; export * from "./image_encoder";
export * from "./utils"; export * from "./utils";
export * from "./printers";

View File

@ -7,12 +7,12 @@ import {
NiimbotPacket, NiimbotPacket,
PacketGenerator, PacketGenerator,
PrinterInfoType, PrinterInfoType,
PrinterModelId,
ResponseCommandId, ResponseCommandId,
SoundSettingsItemType, SoundSettingsItemType,
SoundSettingsType, SoundSettingsType,
} from "."; } from ".";
import { NiimbotAbstractClient, Utils, Validators } from ".."; import { NiimbotAbstractClient, Utils, Validators } from "..";
import { PrinterModel } from "../printers";
import { SequentialDataReader } from "./data_reader"; import { SequentialDataReader } from "./data_reader";
export class PrintError extends Error { export class PrintError extends Error {
@ -89,10 +89,7 @@ export class Abstraction {
if (packet.command === ResponseCommandId.In_PrintError) { if (packet.command === ResponseCommandId.In_PrintError) {
Validators.u8ArrayLengthEquals(packet.data, 1); Validators.u8ArrayLengthEquals(packet.data, 1);
throw new PrintError( throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet received)`, packet.data[0]);
`Print error (${ResponseCommandId[packet.command]} packet received)`,
packet.data[0]
);
} }
Validators.u8ArrayLengthAtLeast(packet.data, 4); // can be 8, 10, but ignore it for now Validators.u8ArrayLengthAtLeast(packet.data, 4); // can be 8, 10, but ignore it for now
@ -105,7 +102,10 @@ export class Abstraction {
if (packet.dataLength === 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.command]} packet flag)`, error);
if (error !== 0) {
throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet flag)`, error);
}
} }
return { page, pagePrintProgress, pageFeedProgress }; return { page, pagePrintProgress, pageFeedProgress };
@ -122,34 +122,34 @@ 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.dataLength > 12) { if (packet.dataLength > 12) {
supportColor = packet.data[10]; supportColor = packet.data[10];
let n = packet.data[11] * 100 + packet.data[12]; let n = packet.data[11] * 100 + packet.data[12];
if (n >= 204 && n < 300) { if (n >= 204 && n < 300) {
protocolVersion = 3; protocolVersion = 3;
}
if (n >= 301) {
protocolVersion = 4;
}
} }
if (n >= 301) {
protocolVersion = 4;
}
}
return { return {
supportColor, supportColor,
protocolVersion protocolVersion,
} };
} }
public async getPrinterModel(): Promise<PrinterModelId> { public async getPrinterModel(): Promise<PrinterModel> {
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterModelId)); const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterModelId));
Validators.u8ArrayLengthEquals(packet.data, 2); Validators.u8ArrayLengthEquals(packet.data, 2);
const id = Utils.bytesToI16(packet.data); const id = Utils.bytesToI16(packet.data);
if (id in PrinterModelId) { if (id in PrinterModel) {
return id as PrinterModelId; return id as PrinterModel;
} }
return PrinterModelId.UNKNOWN; return PrinterModel.UNKNOWN;
} }
/** Read paper nfc tag info */ /** Read paper nfc tag info */
@ -224,7 +224,7 @@ export class Abstraction {
} }
r.end(); r.end();
const model = this.client.getPrinterConfig().model ?? PrinterModelId.UNKNOWN; const model = this.client.getPrinterInfo().model ?? PrinterModel.UNKNOWN;
if (![512, 514, 513, 2304, 1792, 3584, 5120, 2560, 3840, 4352, 272].includes(model)) { if (![512, 514, 513, 2304, 1792, 3584, 5120, 2560, 3840, 4352, 272].includes(model)) {
info.lidClosed = !info.lidClosed; info.lidClosed = !info.lidClosed;
@ -245,6 +245,10 @@ export class Abstraction {
return packet.data[0] as AutoShutdownTime; return packet.data[0] as AutoShutdownTime;
} }
public async setAutoShutDownTime(time: AutoShutdownTime): Promise<void> {
await this.send(PacketGenerator.setAutoShutDownTime(time));
}
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.data, 1); Validators.u8ArrayLengthEquals(packet.data, 1);

View File

@ -27,7 +27,7 @@ export enum RequestCommandId {
SetLabelType = 0x23 /* D11 - 1,5, for D110 able to set 1,2,3,5; see LabelType */, SetLabelType = 0x23 /* D11 - 1,5, for D110 able to set 1,2,3,5; see LabelType */,
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 AntiFake = 0x0b, // some info request (niimbot app), 01 long 02 short
WriteRFID = 0x70, // same as GetVolumeLevel??? WriteRFID = 0x70, // same as GetVolumeLevel???
} }
@ -53,7 +53,7 @@ export enum ResponseCommandId {
In_PrinterInfoPrinterCode = 0x48, In_PrinterInfoPrinterCode = 0x48,
In_PrinterInfoSerialNumber = 0x4b, In_PrinterInfoSerialNumber = 0x4b,
In_PrinterInfoSoftWareVersion = 0x49, In_PrinterInfoSoftWareVersion = 0x49,
In_PrinterInfoUnknown1 = 0x4f, In_PrinterInfoArea = 0x4f,
IN_PrinterStatusData = 0xb5, IN_PrinterStatusData = 0xb5,
In_PrintStatus = 0xb3, In_PrintStatus = 0xb3,
In_PrintError = 0xdb, // For example, sent on SetPageSize when page print is not started In_PrintError = 0xdb, // For example, sent on SetPageSize when page print is not started
@ -66,7 +66,7 @@ export enum ResponseCommandId {
In_SetLabelType = 0x33, In_SetLabelType = 0x33,
In_SetPageSize = 0x14, In_SetPageSize = 0x14,
In_SoundSettings = 0x68, In_SoundSettings = 0x68,
In_Unknown1 = 0xe4, In_OageEnd = 0xe4,
} }
export enum PrinterInfoType { export enum PrinterInfoType {
@ -83,7 +83,7 @@ export enum PrinterInfoType {
HardWareVersion = 12, HardWareVersion = 12,
BluetoothAddress = 13, BluetoothAddress = 13,
PrintMode = 14, PrintMode = 14,
Unknown1 = 15, Area = 15,
} }
export enum SoundSettingsType { export enum SoundSettingsType {
@ -138,67 +138,6 @@ export enum ConnectResult {
FirmwareErrors = 90, FirmwareErrors = 90,
} }
/** Generated from android app (assets/flutter_assets/assets/config/printerList.json) */
export enum PrinterModelId {
UNKNOWN = 0,
T6 = 51715,
TP2M_H = 4609,
B31 = 5632,
B1 = 4096,
M2_H = 4608,
B21_PRO = 785,
P1 = 1024,
T2S = 53250,
B50W = 51714,
T7 = 51717,
B50 = 51713,
Z401 = 2051,
B32R = 2050,
A63 = 2054,
T8S = 2053,
B32 = 2049,
B18S = 3585,
B18 = 3584,
MP3K_W = 4867,
MP3K = 4866,
K3_W = 4865,
K3 = 4864,
B3S_P = 272,
S6 = 261,
B3S = 256,
B3S_V2 = 260,
B3S_V3 = 262,
B3 = 52993,
A203 = 2818,
A20 = 2817,
B203 = 2816,
S1 = 51458,
JC_M90 = 51461,
S3 = 51460,
B11 = 51457,
B21_H = 784,
B21S_C2B = 776,
B21S = 777,
B21_L2B = 769,
B21_C2B = 771,
B21_C2B_V2 = 775,
B21 = 768,
B16 = 1792,
BETTY = 2561,
D101 = 2560,
D110_M = 2320,
HI_D110 = 2305,
D110 = 2304,
D11_H = 528,
D11S = 514,
FUST = 513,
D11 = 512,
ET10 = 5376,
P18 = 1026,
P1S = 1025,
T8 = 51718,
}
export * from "./packet"; export * from "./packet";
export * from "./packet_generator"; export * from "./packet_generator";
export * from "./abstraction"; export * from "./abstraction";

View File

@ -10,6 +10,7 @@ import {
SoundSettingsType, SoundSettingsType,
} from "."; } from ".";
import { EncodedImage, ImageEncoder, ImageRow as ImagePart } from "../image_encoder"; import { EncodedImage, ImageEncoder, ImageRow as ImagePart } from "../image_encoder";
import { ProtocolVersion } from "../printers";
import { Utils } from "../utils"; import { Utils } from "../utils";
export type PrintOptions = { export type PrintOptions = {
@ -18,19 +19,6 @@ export type PrintOptions = {
quantity?: number; quantity?: number;
}; };
export enum ProtocolVariant {
/** Used in D11 */
V1 = 1,
/** Used in B21, D110new */
V2,
/** Used in B16 */
V3,
/** Used in B1 */
V4,
/** Not used */
V5,
}
export class PacketGenerator { export class PacketGenerator {
public static generic( public static generic(
requestId: RequestCommandId, requestId: RequestCommandId,
@ -73,7 +61,7 @@ export class PacketGenerator {
ResponseCommandId.In_PrinterInfoHardWareVersion, ResponseCommandId.In_PrinterInfoHardWareVersion,
ResponseCommandId.In_PrinterInfoBluetoothAddress, ResponseCommandId.In_PrinterInfoBluetoothAddress,
// ResponseCommandId.In_PrinterInfoPrintMode, // ResponseCommandId.In_PrinterInfoPrintMode,
ResponseCommandId.In_PrinterInfoUnknown1, ResponseCommandId.In_PrinterInfoArea,
] ]
); );
} }
@ -334,12 +322,12 @@ export class PacketGenerator {
/* /*
B1 print process example (square in square, 160x240) B1 print process example (square in square, 160x240)
SetDensity 5555 21 0102 22aaaa SetDensity 5555 21 01 02 22aaaa
SetLabelType 5555 23 0101 23aaaa SetLabelType 5555 23 01 01 23aaaa
PrintStart 5555 01 0700010000000000 07aaaa PrintStart 5555 01 07 00010000000000 07aaaa
PageStart 5555 03 0101 03aaaa PageStart 5555 03 01 01 03aaaa
SetPageSize 5555 13 0600a000f00001 44aaaa SetPageSize 5555 13 06 00a000f00001 44aaaa
PrintEmptyRows 5555 84 0300001d 9aaaaa PrintEmptyRows 5555 84 03 00001d 9aaaaa
PrintBitmapRows 5555 85 24 001d 3e3000 04 000000000000003ffffffffffffffffffffffffffffffff0000000000000 86aaaa PrintBitmapRows 5555 85 24 001d 3e3000 04 000000000000003ffffffffffffffffffffffffffffffff0000000000000 86aaaa
PrintBitmapRows 5555 85 24 0021 b83000 21 000000000000003c000000000000000000000000000000f0000000000000 e5aaaa PrintBitmapRows 5555 85 24 0021 b83000 21 000000000000003c000000000000000000000000000000f0000000000000 e5aaaa
PrintBitmapRows 5555 85 24 0042 9e3000 04 000000000000003c000000000007fffffe000000000000f0000000000000 7caaaa PrintBitmapRows 5555 85 24 0042 9e3000 04 000000000000003c000000000007fffffe000000000000f0000000000000 7caaaa
@ -347,10 +335,10 @@ export class PacketGenerator {
PrintBitmapRows 5555 85 24 005a 9e3000 04 000000000000003c000000000007fffffe000000000000f0000000000000 64aaaa PrintBitmapRows 5555 85 24 005a 9e3000 04 000000000000003c000000000007fffffe000000000000f0000000000000 64aaaa
PrintBitmapRows 5555 85 24 005e b83000 23 000000000000003c000000000000000000000000000000f0000000000000 98aaaa PrintBitmapRows 5555 85 24 005e b83000 23 000000000000003c000000000000000000000000000000f0000000000000 98aaaa
PrintBitmapRows 5555 85 24 0081 3e3000 04 000000000000003ffffffffffffffffffffffffffffffff0000000000000 1aaaaa PrintBitmapRows 5555 85 24 0081 3e3000 04 000000000000003ffffffffffffffffffffffffffffffff0000000000000 1aaaaa
PrintEmptyRows 5555 84 0300851b19aaaa PrintEmptyRows 5555 84 03 00851b19aaaa
PageEnd 5555 e3 0101 e3aaaa PageEnd 5555 e3 01 01 e3aaaa
PrintStatus 5555 a3 0101 a3aaaa (alot) PrintStatus 5555 a3 01 01 a3aaaa (alot)
PrintEnd 5555 f3 0101 f3aaaa PrintEnd 5555 f3 01 01 f3aaaa
You should send PrintEnd manually after this sequence (after print finished) You should send PrintEnd manually after this sequence (after print finished)
@ -368,17 +356,17 @@ export class PacketGenerator {
} }
public static generatePrintSequence( public static generatePrintSequence(
variant: ProtocolVariant, protoVersion: ProtocolVersion,
image: EncodedImage, image: EncodedImage,
options?: PrintOptions options?: PrintOptions
): NiimbotPacket[] { ): NiimbotPacket[] {
switch (variant) { switch (protoVersion) {
case ProtocolVariant.V3: case ProtocolVersion.V3:
return PacketGenerator.generatePrintSequenceV3(image, options); return PacketGenerator.generatePrintSequenceV3(image, options);
case ProtocolVariant.V4: case ProtocolVersion.V4:
return PacketGenerator.generatePrintSequenceV4(image, options); return PacketGenerator.generatePrintSequenceV4(image, options);
default: default:
throw new Error("Not implemented"); throw new Error(`PrintTaskVersion ${protoVersion} Not implemented`);
} }
} }
} }

184
niimbluelib/src/printers.ts Normal file
View File

@ -0,0 +1,184 @@
export enum PrinterModel {
UNKNOWN = 0,
A20 = 2817,
A203 = 2818,
A63 = 2054,
A8 = 1280,
B11 = 51457,
B16 = 1792,
B18 = 3584,
B18S = 3585,
B20 = 4608,
B201__B1 = 4096,
B203 = 2816,
B21__B21_OLD = 768,
B21_C2B = 771,
B21_C2B_ZX__B21_C2B_V2 = 775,
B21_C2W = 772,
B21_C3W = 774,
B21_H = 784,
B21_L2B = 769,
B21_L2W = 770,
B21_PRO = 785,
B21S = 777,
B21S_C2B = 776,
B3 = 52993,
B31 = 5632,
B32 = 2049,
B32_R = 2050,
B3S = 256,
B3S_GD__B3S_V3 = 262,
B3S_P = 272,
B3S_ZX__B3S_V2 = 260,
B50 = 51713,
B50W = 51714,
BETTY = 2561,
C1 = 5120,
D101 = 2560,
D11 = 512,
D11_H = 528,
D110 = 2304,
D110_M = 2320,
D11S = 514,
D61 = 1536,
ET10 = 5376,
FUST = 513,
H1 = 4352,
H10 = 3840,
HI_D110 = 2305,
JC_M90 = 51461,
K1__K3 = 4864,
K1S__K3_W = 4865,
MP3K = 4866,
MP3K_W = 4867,
P1 = 1024,
P18 = 1026,
P1S = 1025,
S1 = 51458,
S3 = 51460,
S6 = 257,
S6_1 = 258,
S6_2 = 261,
T2S = 53250,
T6 = 51715,
T7 = 51717,
T8 = 51718,
T8S = 2053,
TP2M_H = 4609,
TSC = 255,
Z401 = 2051,
Z401_R = 2052,
}
export enum ProtocolVersion {
UNKNOWN = 0,
/** Used in D11 */
V1,
/** Used in B21, D110new */
V2,
/** Used in B16 */
V3,
/** Used in B1 */
V4,
/** Not used */
V5,
}
export interface PrinterCapabilities {
printTaskVersion: ProtocolVersion;
}
import M = PrinterModel;
export const getPrinterCapabilities = (id: PrinterModel): PrinterCapabilities => {
let printTaskVersion = ProtocolVersion.UNKNOWN;
switch (id) {
case M.D11:
case M.D11_H:
case M.D11S:
printTaskVersion = ProtocolVersion.V1;
break;
case M.D110:
case M.D110_M:
printTaskVersion = ProtocolVersion.V3;
break;
case M.B201__B1:
printTaskVersion = ProtocolVersion.V4;
break;
// todo: find other models info
case M.UNKNOWN:
case M.A20:
case M.A203:
case M.A63:
case M.A8:
case M.B11:
case M.B16:
case M.B18:
case M.B18S:
case M.B20:
case M.B203:
case M.B21__B21_OLD:
case M.B21_C2B:
case M.B21_C2B_ZX__B21_C2B_V2:
case M.B21_C2W:
case M.B21_C3W:
case M.B21_H:
case M.B21_L2B:
case M.B21_L2W:
case M.B21_PRO:
case M.B21S:
case M.B21S_C2B:
case M.B3:
case M.B31:
case M.B32:
case M.B32_R:
case M.B3S:
case M.B3S_GD__B3S_V3:
case M.B3S_P:
case M.B3S_ZX__B3S_V2:
case M.B50:
case M.B50W:
case M.BETTY:
case M.C1:
case M.D101:
case M.D61:
case M.ET10:
case M.FUST:
case M.H1:
case M.H10:
case M.HI_D110:
case M.JC_M90:
case M.K1__K3:
case M.K1S__K3_W:
case M.MP3K:
case M.MP3K_W:
case M.P1:
case M.P18:
case M.P1S:
case M.S1:
case M.S3:
case M.S6:
case M.S6_1:
case M.S6_2:
case M.T2S:
case M.T6:
case M.T7:
case M.T8:
case M.T8S:
case M.TP2M_H:
case M.TSC:
case M.Z401:
case M.Z401_R:
break;
default:
((_id: never) => {
throw new Error(`Printer model ${_id} was unhandled!`);
})(id);
}
return { printTaskVersion };
};