Working tree changes 2024-07-28 00:00
All checks were successful
Test project build / Build (push) Successful in 1m6s
All checks were successful
Test project build / Build (push) Successful in 1m6s
This commit is contained in:
parent
16324d5f5f
commit
266a392401
28
niimblue/src/lib/GenericObjectParamsControls.svelte
Normal file
28
niimblue/src/lib/GenericObjectParamsControls.svelte
Normal 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>
|
@ -9,6 +9,7 @@
|
||||
import FaIcon from "./FaIcon.svelte";
|
||||
import PrintPreview from "./PrintPreview.svelte";
|
||||
import TextParamsPanel from "./TextParamsControls.svelte";
|
||||
import GenericObjectParamsControls from "./GenericObjectParamsControls.svelte";
|
||||
|
||||
let htmlCanvas: HTMLCanvasElement;
|
||||
let fabricCanvas: fabric.Canvas;
|
||||
@ -94,9 +95,10 @@
|
||||
if (name === "text") {
|
||||
const obj = new fabric.IText("Text", {
|
||||
...fabricObjectDefaults,
|
||||
fontFamily: "Arial",
|
||||
fontFamily: "Arial"
|
||||
});
|
||||
fabricCanvas.add(obj);
|
||||
obj.center();
|
||||
} else if (name === "line") {
|
||||
const obj = new fabric.Line([10, 10, 10 + defaultSize, 10], {
|
||||
...fabricObjectDefaults,
|
||||
@ -111,8 +113,8 @@
|
||||
mt: false,
|
||||
mb: false,
|
||||
});
|
||||
|
||||
fabricCanvas.add(obj);
|
||||
obj.centerV();
|
||||
} else if (name === "circle") {
|
||||
const obj = new fabric.Circle({
|
||||
...fabricObjectDefaults,
|
||||
@ -122,6 +124,7 @@
|
||||
strokeWidth: 3,
|
||||
});
|
||||
fabricCanvas.add(obj);
|
||||
obj.centerV();
|
||||
} else if (name === "rectangle") {
|
||||
const obj = new fabric.Rect({
|
||||
...fabricObjectDefaults,
|
||||
@ -132,6 +135,7 @@
|
||||
strokeWidth: 3,
|
||||
});
|
||||
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>
|
||||
{/if}
|
||||
|
||||
{#if selectedCount === 1}
|
||||
{#if selectedObject && selectedCount === 1}
|
||||
<button class="btn btn-sm btn-secondary me-1" on:click={cloneSelected}><FaIcon icon="clone" /></button>
|
||||
<GenericObjectParamsControls {selectedObject} valueUpdated={() => fabricCanvas.requestRenderAll()} />
|
||||
{/if}
|
||||
|
||||
{#if selectedObject instanceof fabric.IText}
|
||||
|
@ -7,19 +7,14 @@
|
||||
import {
|
||||
type EncodedImage,
|
||||
ImageEncoder,
|
||||
Utils,
|
||||
LabelType,
|
||||
PacketGenerator,
|
||||
ProtocolVariant,
|
||||
ResponseCommandId,
|
||||
PrintError,
|
||||
ProtocolVersion,
|
||||
} from "@mmote/niimbluelib";
|
||||
import type { LabelProps } from "../types";
|
||||
import FaIcon from "./FaIcon.svelte";
|
||||
|
||||
export let onClosed: () => void;
|
||||
export let labelProps: LabelProps;
|
||||
|
||||
export let imageCallback: () => string;
|
||||
|
||||
let modalElement: HTMLElement;
|
||||
@ -34,7 +29,7 @@
|
||||
let thresholdValue: number = 140;
|
||||
let imgData: ImageData;
|
||||
let imgContext: CanvasRenderingContext2D;
|
||||
let protoVariant: ProtocolVariant = ProtocolVariant.V3;
|
||||
let printTaskVersion: ProtocolVersion = ProtocolVersion.V3;
|
||||
let statusTimer: NodeJS.Timeout | undefined = undefined;
|
||||
let printError: boolean = false;
|
||||
|
||||
@ -54,7 +49,7 @@
|
||||
const onPrint = async () => {
|
||||
printError = false;
|
||||
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++) {
|
||||
sendProgress = Math.round(((i + 1) / packets.length) * 100);
|
||||
@ -92,15 +87,14 @@
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
// create image from fabric canvas to work with
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
previewCanvas.width = img.width;
|
||||
previewCanvas.height = img.height;
|
||||
|
||||
imgContext = previewCanvas.getContext("2d")!;
|
||||
imgContext.drawImage(img, 0, 0, img.width, img.height);
|
||||
imgData = imgContext.getImageData(0, 0, img.width, img.height);
|
||||
|
||||
updatePreview();
|
||||
};
|
||||
img.src = imageCallback();
|
||||
@ -111,6 +105,12 @@
|
||||
cancelPrint();
|
||||
onClosed();
|
||||
});
|
||||
|
||||
const taskVer = $printerClient?.getCapabilities().printTaskVersion;
|
||||
if (taskVer !== undefined && taskVer !== ProtocolVersion.UNKNOWN) {
|
||||
console.log(`Detected print task version: ${ProtocolVersion[taskVer]}`)
|
||||
printTaskVersion = taskVer;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
@ -192,10 +192,13 @@
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Protocol variant</span>
|
||||
<select class="form-select" bind:value={protoVariant}>
|
||||
<option value={ProtocolVariant.V3}>V3 - D110</option>
|
||||
<option value={ProtocolVariant.V4}>V4 - B1</option>
|
||||
<span class="input-group-text">Print task version</span>
|
||||
<select class="form-select" bind:value={printTaskVersion}>
|
||||
<option value={ProtocolVersion.V1} disabled>V1 - NOT IMPLEMENTED</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>
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<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 type { ConnectionType } from "../types";
|
||||
import FaIcon from "./FaIcon.svelte";
|
||||
@ -36,11 +36,6 @@
|
||||
// clearInterval(timer);
|
||||
$printerClient.stopHeartbeat();
|
||||
};
|
||||
|
||||
const test3 = async () => {
|
||||
const id = await $printerClient.abstraction.getPrinterModel();
|
||||
alert(`Printer model: ${PrinterModelId[id]}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="input-group flex-nowrap justify-content-end">
|
||||
@ -49,24 +44,33 @@
|
||||
><FaIcon icon="gear" />
|
||||
</button>
|
||||
<div class="dropdown-menu p-1">
|
||||
<div>
|
||||
Model: {$printerConfig.model === undefined ? "?" : (PrinterModel[$printerConfig.model] ?? "Unknown")}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Printer info:
|
||||
<pre>{JSON.stringify($printerConfig, null, 1)}</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Rfid info:
|
||||
<pre>{JSON.stringify(rfidInfo || {}, null, 1)}</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Heartbeat data:
|
||||
<pre>{JSON.stringify($heartbeatData, null, 1)}</pre>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
Rfid info:
|
||||
<pre>{JSON.stringify(rfidInfo || {}, null, 1)}</pre>
|
||||
Tests
|
||||
</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={stopHeartbeat}>stop heartbeat (test)</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={test3}>Guess printer model</button>
|
||||
|
||||
<button class="btn btn-sm btn-primary" on:click={getRfidInfo}>Rfid</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={startHeartbeat}>Heartbeat on</button>
|
||||
<button class="btn btn-sm btn-primary" on:click={stopHeartbeat}>Heartbeat off</button>
|
||||
</div>
|
||||
<span class="input-group-text">{$connectedPrinterName}</span>
|
||||
{:else}
|
||||
|
@ -54,14 +54,17 @@
|
||||
{#if selectedText}
|
||||
<!-- <div class="d-flex flex-wrap gap-1"> -->
|
||||
<button
|
||||
title="Align text: Left"
|
||||
class="btn btn-sm {selectedText.textAlign === 'left' ? 'btn-secondary' : ''}"
|
||||
on:click={() => setAlign("left")}><FaIcon icon="align-left" /></button
|
||||
>
|
||||
<button
|
||||
title="Align text: Center"
|
||||
class="btn btn-sm {selectedText.textAlign === 'center' ? 'btn-secondary' : ''}"
|
||||
on:click={() => setAlign("center")}><FaIcon icon="align-center" /></button
|
||||
>
|
||||
<button
|
||||
title="Align text: Right"
|
||||
class="btn btn-sm {selectedText.textAlign === 'right' ? 'btn-secondary' : ''}"
|
||||
on:click={() => setAlign("right")}><FaIcon icon="align-right" /></button
|
||||
>
|
||||
|
@ -2,25 +2,12 @@ export const copyImageData = (iData: ImageData): ImageData => {
|
||||
return new ImageData(new Uint8ClampedArray(iData.data), iData.width, iData.height);
|
||||
};
|
||||
|
||||
export const convertImgDataToBlackAndWhite = (iData: ImageData, threshold = 0xff): ImageData => {
|
||||
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;
|
||||
};
|
||||
// Original code is taken from https://github.com/NielsLeenheer/CanvasDither
|
||||
|
||||
/**
|
||||
* Change the image to blank and white using a simple threshold
|
||||
*
|
||||
*
|
||||
* @param {object} image The imageData of a Canvas 2d context
|
||||
* @param {number} threshold Threshold value (0-255)
|
||||
* @return {object} The resulting imageData
|
||||
@ -29,7 +16,6 @@ export const convertImgDataToBlackAndWhite = (iData: ImageData, threshold = 0xff
|
||||
export const threshold = (image: ImageData, threshold: number): ImageData => {
|
||||
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 value = luminance < threshold ? 0 : 255;
|
||||
image.data.fill(value, i, i + 3);
|
||||
}
|
||||
@ -37,68 +23,11 @@ export const threshold = (image: ImageData, threshold: number): ImageData => {
|
||||
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
|
||||
*
|
||||
* @param {object} image The imageData of a Canvas 2d context
|
||||
* @param {number} threshold Threshold value (0-255)
|
||||
* @return {object} The resulting imageData
|
||||
*
|
||||
*/
|
||||
@ -125,35 +54,3 @@ export const atkinson = (image: ImageData, threshold: number): ImageData => {
|
||||
|
||||
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;
|
||||
}
|
@ -52,7 +52,7 @@ export const initClient = (connectionType: ConnectionType) => {
|
||||
console.log("onConnect");
|
||||
connectionState.set("connected");
|
||||
connectedPrinterName.set(e.info.deviceName ?? "unknown");
|
||||
printerConfig.set(newClient.getPrinterConfig());
|
||||
printerConfig.set(newClient.getPrinterInfo());
|
||||
});
|
||||
|
||||
newClient.addEventListener("disconnect", () => {
|
||||
|
@ -40,7 +40,7 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
|
||||
const disconnectListener = async () => {
|
||||
this.gattServer = undefined;
|
||||
this.channel = undefined;
|
||||
this.printerConfig = {};
|
||||
this.info = {};
|
||||
this.stopHeartbeat();
|
||||
this.dispatchTypedEvent("disconnect", new DisconnectEvent());
|
||||
device.removeEventListener("gattserverdisconnected", disconnectListener);
|
||||
@ -73,14 +73,14 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
|
||||
|
||||
try {
|
||||
await this.initialNegotiate();
|
||||
await this.fetchPrinterConfig();
|
||||
await this.fetchPrinterInfo();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const result: ConnectionInfo = {
|
||||
deviceName: device.name,
|
||||
result: this.printerConfig.connectResult ?? ConnectResult.FirmwareErrors
|
||||
result: this.info.connectResult ?? ConnectResult.FirmwareErrors
|
||||
};
|
||||
|
||||
this.dispatchTypedEvent("connect", new ConnectEvent(result));
|
||||
@ -97,7 +97,7 @@ export class NiimbotBluetoothClient extends NiimbotAbstractClient {
|
||||
this.gattServer?.disconnect();
|
||||
this.gattServer = undefined;
|
||||
this.channel = undefined;
|
||||
this.printerConfig = {};
|
||||
this.info = {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 { ClientEventMap, HeartbeatEvent } from "./events";
|
||||
import { Abstraction } from "../packets/abstraction";
|
||||
import { getPrinterCapabilities, PrinterCapabilities, PrinterModel } from "../printers";
|
||||
|
||||
export type ConnectionInfo = {
|
||||
deviceName?: string;
|
||||
@ -11,7 +12,7 @@ export type ConnectionInfo = {
|
||||
export interface PrinterConfig {
|
||||
connectResult?: ConnectResult;
|
||||
protocolVersion?: number;
|
||||
model?: PrinterModelId;
|
||||
model?: PrinterModel;
|
||||
serial?: string;
|
||||
mac?: string;
|
||||
charge?: BatteryChargeLevel;
|
||||
@ -20,7 +21,7 @@ export interface PrinterConfig {
|
||||
|
||||
export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEventMap> {
|
||||
public readonly abstraction: Abstraction;
|
||||
protected printerConfig: PrinterConfig = {};
|
||||
protected info: PrinterConfig = {};
|
||||
private heartbeatTimer?: NodeJS.Timeout;
|
||||
|
||||
constructor() {
|
||||
@ -56,7 +57,7 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
|
||||
|
||||
/** Send "connect" packet and fetch the protocol version */
|
||||
protected async initialNegotiate(): Promise<void> {
|
||||
const cfg = this.printerConfig;
|
||||
const cfg = this.info;
|
||||
cfg.connectResult = await this.abstraction.connectResult();
|
||||
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());
|
||||
this.printerConfig.model = await this.abstraction.getPrinterModel();
|
||||
this.printerConfig.serial = await this.abstraction.getPrinterSerialNumber();
|
||||
this.printerConfig.mac = await this.abstraction.getPrinterBluetoothMacAddress();
|
||||
this.printerConfig.charge = await this.abstraction.getBatteryChargeLevel();
|
||||
this.printerConfig.autoShutdownTime = await this.abstraction.getAutoShutDownTime();
|
||||
return this.printerConfig;
|
||||
this.info.model = await this.abstraction.getPrinterModel();
|
||||
this.info.serial = await this.abstraction.getPrinterSerialNumber();
|
||||
this.info.mac = await this.abstraction.getPrinterBluetoothMacAddress();
|
||||
this.info.charge = await this.abstraction.getBatteryChargeLevel();
|
||||
this.info.autoShutdownTime = await this.abstraction.getAutoShutDownTime();
|
||||
return this.info;
|
||||
}
|
||||
|
||||
public getPrinterConfig(): PrinterConfig {
|
||||
return this.printerConfig;
|
||||
public getPrinterInfo(): PrinterConfig {
|
||||
return this.info;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,6 +103,11 @@ export abstract class NiimbotAbstractClient extends TypedEventTarget<ClientEvent
|
||||
public isHeartbeatStarted(): boolean {
|
||||
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";
|
||||
|
@ -30,14 +30,14 @@ export class NiimbotSerialClient extends NiimbotAbstractClient {
|
||||
|
||||
try {
|
||||
await this.initialNegotiate();
|
||||
await this.fetchPrinterConfig();
|
||||
await this.fetchPrinterInfo();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const result: ConnectionInfo = {
|
||||
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));
|
||||
|
@ -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)
|
||||
*/
|
||||
public static indexPixels(data: Uint8Array): Uint8Array {
|
||||
|
@ -2,3 +2,4 @@ export * from "./client";
|
||||
export * from "./packets";
|
||||
export * from "./image_encoder";
|
||||
export * from "./utils";
|
||||
export * from "./printers";
|
||||
|
@ -7,12 +7,12 @@ import {
|
||||
NiimbotPacket,
|
||||
PacketGenerator,
|
||||
PrinterInfoType,
|
||||
PrinterModelId,
|
||||
ResponseCommandId,
|
||||
SoundSettingsItemType,
|
||||
SoundSettingsType,
|
||||
} from ".";
|
||||
import { NiimbotAbstractClient, Utils, Validators } from "..";
|
||||
import { PrinterModel } from "../printers";
|
||||
import { SequentialDataReader } from "./data_reader";
|
||||
|
||||
export class PrintError extends Error {
|
||||
@ -89,10 +89,7 @@ export class Abstraction {
|
||||
|
||||
if (packet.command === ResponseCommandId.In_PrintError) {
|
||||
Validators.u8ArrayLengthEquals(packet.data, 1);
|
||||
throw new PrintError(
|
||||
`Print error (${ResponseCommandId[packet.command]} packet received)`,
|
||||
packet.data[0]
|
||||
);
|
||||
throw new PrintError(`Print error (${ResponseCommandId[packet.command]} packet received)`, packet.data[0]);
|
||||
}
|
||||
|
||||
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) {
|
||||
r.skip(2);
|
||||
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 };
|
||||
@ -122,34 +122,34 @@ export class Abstraction {
|
||||
const packet = await this.send(PacketGenerator.getPrinterStatusData());
|
||||
let supportColor = 0;
|
||||
|
||||
if (packet.dataLength > 12) {
|
||||
supportColor = packet.data[10];
|
||||
if (packet.dataLength > 12) {
|
||||
supportColor = packet.data[10];
|
||||
|
||||
let n = packet.data[11] * 100 + packet.data[12];
|
||||
if (n >= 204 && n < 300) {
|
||||
protocolVersion = 3;
|
||||
}
|
||||
if (n >= 301) {
|
||||
protocolVersion = 4;
|
||||
}
|
||||
let n = packet.data[11] * 100 + packet.data[12];
|
||||
if (n >= 204 && n < 300) {
|
||||
protocolVersion = 3;
|
||||
}
|
||||
if (n >= 301) {
|
||||
protocolVersion = 4;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
supportColor,
|
||||
protocolVersion
|
||||
}
|
||||
protocolVersion,
|
||||
};
|
||||
}
|
||||
|
||||
public async getPrinterModel(): Promise<PrinterModelId> {
|
||||
public async getPrinterModel(): Promise<PrinterModel> {
|
||||
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.PrinterModelId));
|
||||
Validators.u8ArrayLengthEquals(packet.data, 2);
|
||||
|
||||
const id = Utils.bytesToI16(packet.data);
|
||||
|
||||
if (id in PrinterModelId) {
|
||||
return id as PrinterModelId;
|
||||
if (id in PrinterModel) {
|
||||
return id as PrinterModel;
|
||||
}
|
||||
return PrinterModelId.UNKNOWN;
|
||||
return PrinterModel.UNKNOWN;
|
||||
}
|
||||
|
||||
/** Read paper nfc tag info */
|
||||
@ -224,7 +224,7 @@ export class Abstraction {
|
||||
}
|
||||
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)) {
|
||||
info.lidClosed = !info.lidClosed;
|
||||
@ -245,6 +245,10 @@ export class Abstraction {
|
||||
return packet.data[0] as AutoShutdownTime;
|
||||
}
|
||||
|
||||
public async setAutoShutDownTime(time: AutoShutdownTime): Promise<void> {
|
||||
await this.send(PacketGenerator.setAutoShutDownTime(time));
|
||||
}
|
||||
|
||||
public async getLabelType(): Promise<LabelType> {
|
||||
const packet = await this.send(PacketGenerator.getPrinterInfo(PrinterInfoType.LabelType));
|
||||
Validators.u8ArrayLengthEquals(packet.data, 1);
|
||||
|
@ -27,7 +27,7 @@ export enum RequestCommandId {
|
||||
SetLabelType = 0x23 /* D11 - 1,5, for D110 able to set 1,2,3,5; see LabelType */,
|
||||
SetPageSize = 0x13, // 2, 4 or 6 bytes
|
||||
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???
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ export enum ResponseCommandId {
|
||||
In_PrinterInfoPrinterCode = 0x48,
|
||||
In_PrinterInfoSerialNumber = 0x4b,
|
||||
In_PrinterInfoSoftWareVersion = 0x49,
|
||||
In_PrinterInfoUnknown1 = 0x4f,
|
||||
In_PrinterInfoArea = 0x4f,
|
||||
IN_PrinterStatusData = 0xb5,
|
||||
In_PrintStatus = 0xb3,
|
||||
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_SetPageSize = 0x14,
|
||||
In_SoundSettings = 0x68,
|
||||
In_Unknown1 = 0xe4,
|
||||
In_OageEnd = 0xe4,
|
||||
}
|
||||
|
||||
export enum PrinterInfoType {
|
||||
@ -83,7 +83,7 @@ export enum PrinterInfoType {
|
||||
HardWareVersion = 12,
|
||||
BluetoothAddress = 13,
|
||||
PrintMode = 14,
|
||||
Unknown1 = 15,
|
||||
Area = 15,
|
||||
}
|
||||
|
||||
export enum SoundSettingsType {
|
||||
@ -138,67 +138,6 @@ export enum ConnectResult {
|
||||
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_generator";
|
||||
export * from "./abstraction";
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
SoundSettingsType,
|
||||
} from ".";
|
||||
import { EncodedImage, ImageEncoder, ImageRow as ImagePart } from "../image_encoder";
|
||||
import { ProtocolVersion } from "../printers";
|
||||
import { Utils } from "../utils";
|
||||
|
||||
export type PrintOptions = {
|
||||
@ -18,19 +19,6 @@ export type PrintOptions = {
|
||||
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 {
|
||||
public static generic(
|
||||
requestId: RequestCommandId,
|
||||
@ -73,7 +61,7 @@ export class PacketGenerator {
|
||||
ResponseCommandId.In_PrinterInfoHardWareVersion,
|
||||
ResponseCommandId.In_PrinterInfoBluetoothAddress,
|
||||
// ResponseCommandId.In_PrinterInfoPrintMode,
|
||||
ResponseCommandId.In_PrinterInfoUnknown1,
|
||||
ResponseCommandId.In_PrinterInfoArea,
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -334,12 +322,12 @@ export class PacketGenerator {
|
||||
/*
|
||||
B1 print process example (square in square, 160x240)
|
||||
|
||||
SetDensity 5555 21 0102 22aaaa
|
||||
SetLabelType 5555 23 0101 23aaaa
|
||||
PrintStart 5555 01 0700010000000000 07aaaa
|
||||
PageStart 5555 03 0101 03aaaa
|
||||
SetPageSize 5555 13 0600a000f00001 44aaaa
|
||||
PrintEmptyRows 5555 84 0300001d 9aaaaa
|
||||
SetDensity 5555 21 01 02 22aaaa
|
||||
SetLabelType 5555 23 01 01 23aaaa
|
||||
PrintStart 5555 01 07 00010000000000 07aaaa
|
||||
PageStart 5555 03 01 01 03aaaa
|
||||
SetPageSize 5555 13 06 00a000f00001 44aaaa
|
||||
PrintEmptyRows 5555 84 03 00001d 9aaaaa
|
||||
PrintBitmapRows 5555 85 24 001d 3e3000 04 000000000000003ffffffffffffffffffffffffffffffff0000000000000 86aaaa
|
||||
PrintBitmapRows 5555 85 24 0021 b83000 21 000000000000003c000000000000000000000000000000f0000000000000 e5aaaa
|
||||
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 005e b83000 23 000000000000003c000000000000000000000000000000f0000000000000 98aaaa
|
||||
PrintBitmapRows 5555 85 24 0081 3e3000 04 000000000000003ffffffffffffffffffffffffffffffff0000000000000 1aaaaa
|
||||
PrintEmptyRows 5555 84 0300851b19aaaa
|
||||
PageEnd 5555 e3 0101 e3aaaa
|
||||
PrintStatus 5555 a3 0101 a3aaaa (alot)
|
||||
PrintEnd 5555 f3 0101 f3aaaa
|
||||
PrintEmptyRows 5555 84 03 00851b19aaaa
|
||||
PageEnd 5555 e3 01 01 e3aaaa
|
||||
PrintStatus 5555 a3 01 01 a3aaaa (alot)
|
||||
PrintEnd 5555 f3 01 01 f3aaaa
|
||||
|
||||
|
||||
You should send PrintEnd manually after this sequence (after print finished)
|
||||
@ -368,17 +356,17 @@ export class PacketGenerator {
|
||||
}
|
||||
|
||||
public static generatePrintSequence(
|
||||
variant: ProtocolVariant,
|
||||
protoVersion: ProtocolVersion,
|
||||
image: EncodedImage,
|
||||
options?: PrintOptions
|
||||
): NiimbotPacket[] {
|
||||
switch (variant) {
|
||||
case ProtocolVariant.V3:
|
||||
switch (protoVersion) {
|
||||
case ProtocolVersion.V3:
|
||||
return PacketGenerator.generatePrintSequenceV3(image, options);
|
||||
case ProtocolVariant.V4:
|
||||
case ProtocolVersion.V4:
|
||||
return PacketGenerator.generatePrintSequenceV4(image, options);
|
||||
default:
|
||||
throw new Error("Not implemented");
|
||||
throw new Error(`PrintTaskVersion ${protoVersion} Not implemented`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
184
niimbluelib/src/printers.ts
Normal file
184
niimbluelib/src/printers.ts
Normal 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 };
|
||||
};
|
Reference in New Issue
Block a user