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,8 +102,11 @@ 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();
if (error !== 0) {
throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet flag)`, error); throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet flag)`, error);
} }
}
return { page, pagePrintProgress, pageFeedProgress }; return { page, pagePrintProgress, pageFeedProgress };
} }
@ -136,20 +136,20 @@ export class Abstraction {
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,
] ]
); );
} }
@ -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 };
};