Update docs

This commit is contained in:
MultiMote 2024-11-12 09:36:38 +03:00
parent d1a9ce6b41
commit 6f4b4fc99e
26 changed files with 221 additions and 40 deletions

@ -2,7 +2,7 @@
title: NIIMBOT hardware interfacing
---
# Hardware interfacing
# NIIMBOT hardware interfacing
## Bluetooth
@ -10,7 +10,7 @@ NIIMBOT printers have two bluetooth addresses.
In case of D110 :
* `26:03:03:c3:f9:11` - low energy
* `26:03:03:C3:F9:11` - low energy
* `03:26:03:C3:F9:11` - classic
### Bluetooth Low Energy
@ -19,7 +19,7 @@ You can interact with printer through a specific BLE characteristic.
To find what characteristic is suitable for this:
1. Find services which have UUID length > 4.
2. Find characteristic in these services which have `NOTIFY` and `WRITE_WITHOUT_RESPONSE` properties.
2. Find characteristic in these services which have `NOTIFY` and `WRITE_NO_RESPONSE` properties.
![](proto/characteristic.png)
@ -32,10 +32,14 @@ To send data, write a value without response.
### Bluetooth Classic
Use bluetooth serial.
Use bluetooth serial. The only problem is that packets may be fragmented.
For example, packet `5555d9091f90044c000001000016aaaa` can be received as `5555d9091f90044c000001000016` `aaaa`.
Android [Serial Bluetooth Terminal](https://play.google.com/store/apps/details?id=de.kai_morich.serial_bluetooth_terminal) test:
![](proto/bluetooh_terminal.jpg)
## Serial (USB)
Packet format is same as Bluetooth. The only problem is that packets may be fragmented.
For example, packet `5555d9091f90044c000001000016aaaa` can be received as `5555d9091f90044c000001000016` `aaaa`.
Packet format is same as Bluetooth. Packets may be fragmented.

@ -53,3 +53,10 @@ WIP
| 0xdc | Heartbeat | 0xde, 0xdf, 0xdd, 0xd9 |
| 0xe3 | PageEnd | 0xe4 |
| 0xf3 | PrintEnd | 0xf4 |
## Packets example
* `55 55 40 01 0b 4a aa aa` - get device serial number
* `55 55 1a 01 01 1a aa aa` - get rfid data
* `55 55 58 03 01 01 01 5a aa aa` - enable Bluetooth connection sound
* `55 55 58 03 01 01 00 5b aa aa` - disable Bluetooth connection sound

Binary file not shown.

After

(image error) Size: 53 KiB

@ -27,7 +27,11 @@ class BleConfiguration {
];
}
/** Uses Web Bluetooth API */
/**
* Uses [Web Bluetooth API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API)
*
* @category Client
*/
export class NiimbotBluetoothClient extends NiimbotAbstractClient {
private gattServer?: BluetoothRemoteGATTServer = undefined;
private channel?: BluetoothRemoteGATTCharacteristic = undefined;

@ -14,6 +14,8 @@ import { findPrintTask, PrintTaskName } from "../print_tasks";
/**
* Represents the connection result information.
*
* @category Client
*/
export type ConnectionInfo = {
deviceName?: string;
@ -22,6 +24,8 @@ export type ConnectionInfo = {
/**
* Interface representing printer information.
*
* @category Client
*/
export interface PrinterInfo {
connectResult?: ConnectResult;
@ -40,6 +44,8 @@ export interface PrinterInfo {
/**
* Abstract class representing a client with common functionality for interacting with a printer.
* Hardware interface must be defined after extending this class.
*
* @category Client
*/
export abstract class NiimbotAbstractClient extends EventEmitter<ClientEventMap> {
public readonly abstraction: Abstraction;

@ -11,7 +11,11 @@ import { NiimbotPacket } from "../packets/packet";
import { ConnectResult, PrinterErrorCode, PrintError, ResponseCommandId } from "../packets";
import { Utils, Validators } from "../utils";
/** Uses Web Serial API */
/**
* Uses [Web Serial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API)
*
* @category Client
**/
export class NiimbotSerialClient extends NiimbotAbstractClient {
private port?: SerialPort = undefined;
private writer?: WritableStreamDefaultWriter<Uint8Array> = undefined;

@ -1,6 +1,9 @@
import { ConnectionInfo, PrinterInfo, NiimbotAbstractClient, AbstractPrintTask, HeartbeatData, NiimbotPacket } from ".";
/** Base client event */
/**
* Base client event
* @category Events
*/
export class NiimbotEvent {
readonly type: string;
@ -9,7 +12,10 @@ export class NiimbotEvent {
}
}
/** Fired when client connected to printer and fetched it's information. */
/**
* Fired when client connected to printer and fetched it's information.
* @category Events
*/
export class ConnectEvent extends NiimbotEvent {
info: ConnectionInfo;
constructor(info: ConnectionInfo) {
@ -18,14 +24,20 @@ export class ConnectEvent extends NiimbotEvent {
}
}
/** Fired when client disconnected from printer. */
/**
* Fired when client disconnected from printer.
* @category Events
*/
export class DisconnectEvent extends NiimbotEvent {
constructor() {
super("disconnect");
}
}
/** Fired when packet received, converted to object and validated (head, tail, checksum). */
/**
* Fired when packet received, converted to object and validated (head, tail, checksum).
* @category Events
*/
export class PacketReceivedEvent extends NiimbotEvent {
packet: NiimbotPacket;
constructor(packet: NiimbotPacket) {
@ -34,7 +46,10 @@ export class PacketReceivedEvent extends NiimbotEvent {
}
}
/** Fired when packet object sent. */
/**
* Fired when packet object sent.
* @category Events
*/
export class PacketSentEvent extends NiimbotEvent {
packet: NiimbotPacket;
constructor(packet: NiimbotPacket) {
@ -43,7 +58,10 @@ export class PacketSentEvent extends NiimbotEvent {
}
}
/** Fired when raw packet sent to printer. */
/**
* Fired when raw packet sent to printer.
* @category Events
*/
export class RawPacketSentEvent extends NiimbotEvent {
data: Uint8Array;
constructor(data: Uint8Array) {
@ -52,7 +70,10 @@ export class RawPacketSentEvent extends NiimbotEvent {
}
}
/** Fired when raw packet received from printer. */
/**
* Fired when raw packet received from printer.
* @category Events
*/
export class RawPacketReceivedEvent extends NiimbotEvent {
data: Uint8Array;
constructor(data: Uint8Array) {
@ -61,7 +82,10 @@ export class RawPacketReceivedEvent extends NiimbotEvent {
}
}
/** Fired when heartbeat packet received and parsed. */
/**
* Fired when heartbeat packet received and parsed.
* @category Events
*/
export class HeartbeatEvent extends NiimbotEvent {
data: HeartbeatData;
constructor(data: HeartbeatData) {
@ -70,7 +94,10 @@ export class HeartbeatEvent extends NiimbotEvent {
}
}
/** Fired when no response received after heartbeat packet sent. */
/**
* Fired when no response received after heartbeat packet sent.
* @category Events
*/
export class HeartbeatFailedEvent extends NiimbotEvent {
failedAttempts: number;
constructor(failedAttempts: number) {
@ -79,7 +106,10 @@ export class HeartbeatFailedEvent extends NiimbotEvent {
}
}
/** Fired when info fetched from printer (after {@link NiimbotAbstractClient.fetchPrinterInfo} finished). */
/**
* Fired when info fetched from printer (after {@link NiimbotAbstractClient.fetchPrinterInfo} finished).
* @category Events
*/
export class PrinterInfoFetchedEvent extends NiimbotEvent {
info: PrinterInfo;
constructor(info: PrinterInfo) {
@ -88,7 +118,10 @@ export class PrinterInfoFetchedEvent extends NiimbotEvent {
}
}
/** Fired progress received (during {@link AbstractPrintTask.waitForFinished}). */
/**
* Fired progress received (during {@link AbstractPrintTask.waitForFinished}).
* @category Events
*/
export class PrintProgressEvent extends NiimbotEvent {
/** 0 n */
page: number;
@ -108,6 +141,10 @@ export class PrintProgressEvent extends NiimbotEvent {
}
}
/**
* Event list for {@link NiimbotAbstractClient}.
* @category Events
*/
export type ClientEventMap = {
connect: (event: ConnectEvent) => void;
disconnect: (event: DisconnectEvent) => void;

@ -1,5 +1,6 @@
import { Utils } from ".";
/** @category Image encoder */
export type ImageRow = {
dataType: "void" | "pixels";
rowNumber: number;
@ -8,14 +9,20 @@ export type ImageRow = {
rowData?: Uint8Array;
};
/** @category Image encoder */
export type EncodedImage = {
cols: number;
rows: number;
rowsData: ImageRow[];
};
/** @category Image encoder */
export type PrintDirection = "left" | "top";
/**
* @category Helpers
* @category Image encoder
*/
export class ImageEncoder {
/** printDirection = "left" rotates image for 90 degrees clockwise */
public static encodeCanvas(canvas: HTMLCanvasElement, printDirection: PrintDirection = "left"): EncodedImage {

@ -1,3 +1,7 @@
/**
* @module API
*/
export * from "./client";
export * from "./packets";
export * from "./events";

@ -18,6 +18,9 @@ import { SequentialDataReader } from "./data_reader";
import { NiimbotPacket } from "./packet";
import { PacketGenerator } from "./packet_generator";
/**
* @category Packets
*/
export class PrintError extends Error {
public readonly reasonId: number;
@ -27,6 +30,9 @@ export class PrintError extends Error {
}
}
/**
* @category Packets
*/
export interface PrintStatus {
/** 0 n */
page: number;
@ -35,7 +41,9 @@ export interface PrintStatus {
/** 0 100 */
pageFeedProgress: number;
}
/**
* @category Packets
*/
export interface RfidInfo {
tagPresent: boolean;
uuid: string;
@ -46,7 +54,9 @@ export interface RfidInfo {
consumablesType: LabelType;
}
/** closingState inverted on some printers */
/**
* @category Packets
**/
export interface HeartbeatData {
paperState: number;
rfidReadState: number;
@ -54,18 +64,28 @@ export interface HeartbeatData {
powerLevel: BatteryChargeLevel;
}
/**
* @category Packets
*/
export interface SoundSettings {
category: SoundSettingsType;
item: SoundSettingsItemType;
value: boolean;
}
/**
* @category Packets
*/
export interface PrinterStatusData {
supportColor: number;
protocolVersion: number;
}
/** Not sure for name. */
/**
* Packet sender and parser.
*
* @category Packets
*/
export class Abstraction {
private readonly DEFAULT_PACKET_TIMEOUT: number = 1_000;
private client: NiimbotAbstractClient;

@ -1,4 +1,8 @@
/** Commands IDs from client to printer */
/**
* Commands IDs from client to printer
*
* @category Packets
**/
export enum RequestCommandId {
Invalid = -1,
/** Entire packet should be prefixed with 0x03 */
@ -39,7 +43,11 @@ export enum RequestCommandId {
PrintTestPage = 0x5a,
}
/** Commands IDs from printer to client */
/**
* Commands IDs from printer to client
*
* @category Packets
**/
export enum ResponseCommandId {
Invalid = -1,
In_NotSupported = 0x00,
@ -95,7 +103,11 @@ export enum ResponseCommandId {
import TX = RequestCommandId;
import RX = ResponseCommandId;
/** Map request id to response id. null meant no response expected (one way). */
/**
* Map request id to response id. null meant no response expected (one way).
*
* @category Packets
**/
export const commandsMap: Record<RequestCommandId, ResponseCommandId[] | null> = {
[TX.Invalid]: null,
[TX.PrintBitmapRow]: null,

@ -1,6 +1,10 @@
import { Utils } from "../utils";
/** Utility class to sequentially fetch data from byte array. EOF checks included. */
/**
* Utility class to sequentially fetch data from byte array. EOF checks included.
*
* @category Packets
**/
export class SequentialDataReader {
private bytes: Uint8Array;
private offset: number;

@ -1,6 +1,11 @@
import { Validators } from "../utils";
import { RequestCommandId, ResponseCommandId } from ".";
/**
* NIIMBOT packet object
*
* @category Packets
*/
export class NiimbotPacket {
public static readonly HEAD = new Uint8Array([0x55, 0x55]);
public static readonly TAIL = new Uint8Array([0xaa, 0xaa]);

@ -14,6 +14,7 @@ import { Utils } from "../utils";
/**
* A helper class that generates various types of packets.
* @category Packets
*/
export class PacketGenerator {
/**

@ -1,7 +1,10 @@
import { modelsLibrary } from "../printer_models";
import { RequestCommandId, ResponseCommandId } from "./commands";
/** Sent with {@link RequestCommandId.PrinterInfo} */
/**
* Sent with {@link RequestCommandId.PrinterInfo}
* @category Packets
**/
export enum PrinterInfoType {
Density = 1,
Speed = 2,
@ -19,17 +22,23 @@ export enum PrinterInfoType {
Area = 15,
}
/** @category Packets */
export enum SoundSettingsType {
SetSound = 0x01,
GetSoundState = 0x02,
}
/** @category Packets */
export enum SoundSettingsItemType {
BluetoothConnectionSound = 0x01,
PowerSound = 0x02,
}
/** Sent with {@link RequestCommandId.SetLabelType}. */
/**
* Sent with {@link RequestCommandId.SetLabelType}.
*
* @category Packets
**/
export enum LabelType {
Invalid = 0,
/** Default for most of label printers */
@ -43,6 +52,7 @@ export enum LabelType {
HeatShrinkTube = 11,
}
/** @category Packets */
export enum HeartbeatType {
Advanced1 = 1,
Basic = 2,
@ -50,6 +60,7 @@ export enum HeartbeatType {
Advanced2 = 4,
}
/** @category Packets */
export enum AutoShutdownTime {
/** Usually 15 minutes. */
ShutdownTime1 = 1,
@ -61,7 +72,10 @@ export enum AutoShutdownTime {
ShutdownTime4 = 4,
}
/** Battery charge level */
/**
* Battery charge level
* @category Packets
**/
export enum BatteryChargeLevel {
Charge0 = 0,
Charge25 = 1,
@ -70,7 +84,10 @@ export enum BatteryChargeLevel {
Charge100 = 4,
}
/** {@link ResponseCommandId.In_Connect} status codes. */
/**
* {@link ResponseCommandId.In_Connect} status codes.
* @category Packets
**/
export enum ConnectResult {
Disconnect = 0,
Connected = 1,
@ -79,9 +96,13 @@ export enum ConnectResult {
FirmwareErrors = 90,
}
/** {@link ResponseCommandId.In_PrintError} status codes. */
/**
* {@link ResponseCommandId.In_PrintError} status codes.
* @category Packets
**/
export enum PrinterErrorCode {
CoverOpen = 0x01,
/** No paper */
LackPaper = 0x02,
LowBattery = 0x03,
BatteryException = 0x04,

@ -2,7 +2,10 @@ import { EncodedImage } from "../image_encoder";
import { LabelType } from "../packets";
import { Abstraction } from "../packets/abstraction";
/** Print options for print tasks. */
/**
* Print options for print tasks.
* @category Print tasks
*/
export type PrintOptions = {
/** Printer label type */
labelType: LabelType;
@ -54,6 +57,8 @@ const printOptionsDefaults: PrintOptions = {
* await client.abstraction.printEnd();
* }
* ```
*
* @category Print tasks
**/
export abstract class AbstractPrintTask {
protected abstraction: Abstraction;

@ -2,6 +2,9 @@ import { EncodedImage } from "../image_encoder";
import { PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
/**
* @category Print tasks
*/
export class B1PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([

@ -2,6 +2,9 @@ import { EncodedImage } from "../image_encoder";
import { PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
/**
* @category Print tasks
*/
export class B21V1PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([

@ -2,6 +2,9 @@ import { EncodedImage } from "../image_encoder";
import { PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
/**
* @category Print tasks
*/
export class D110PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([

@ -2,6 +2,9 @@ import { EncodedImage } from "../image_encoder";
import { PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
/**
* @category Print tasks
*/
export class OldD11PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([

@ -2,6 +2,9 @@ import { EncodedImage } from "../image_encoder";
import { PacketGenerator } from "../packets";
import { AbstractPrintTask } from "./AbstractPrintTask";
/**
* @category Print tasks
*/
export class V5PrintTask extends AbstractPrintTask {
override printInit(): Promise<void> {
return this.abstraction.sendAll([

@ -5,7 +5,10 @@ import { D110PrintTask } from "./D110PrintTask";
import { OldD11PrintTask } from "./OldD11PrintTask";
import { V5PrintTask } from "./V5PrintTask";
/** Define available print tasks. */
/**
* Define available print tasks.
* @category Print tasks
*/
export const printTasks = {
D11_V1: OldD11PrintTask,
D110: D110PrintTask,
@ -14,12 +17,19 @@ export const printTasks = {
V5: V5PrintTask,
};
/** Available print task name type. */
/**
* Available print task name type.
* @category Print tasks
*/
export type PrintTaskName = keyof typeof printTasks;
/** List of available print task names. */
/**
* List of available print task names.
* @category Print tasks
*/
export const printTaskNames = Object.keys(printTasks) as PrintTaskName[];
/** @category Printer model library */
export type ModelWithProtocol = {
/** Model */
m: M;
@ -31,7 +41,8 @@ export type ModelWithProtocol = {
* Define print tasks for models.
* Model or model with protocol version can be specified.
* Model with protocol version has priority over just model.
**/
* @category Print tasks
*/
export const modelPrintTasks: Partial<Record<PrintTaskName, (ModelWithProtocol | M)[]>> = {
D11_V1: [M.D11, M.D11S],
B21_V1: [M.B21, M.B21_L2B, M.B21_C2B],
@ -39,7 +50,10 @@ export const modelPrintTasks: Partial<Record<PrintTaskName, (ModelWithProtocol |
B1: [M.D11_H, M.D110_M, M.B1],
};
/** Search print task. */
/**
* Search print task.
* @category Print tasks
*/
export const findPrintTask = (model: M, protocolVersion?: number): PrintTaskName | undefined => {
const tasks = Object.keys(modelPrintTasks) as PrintTaskName[];

@ -4,6 +4,7 @@
import { PrintDirection } from "./image_encoder";
import { LabelType as LT } from "./packets";
/** @category Printer model library */
export enum PrinterModel {
UNKNOWN = "UNKNOWN",
A20 = "A20",
@ -69,6 +70,7 @@ export enum PrinterModel {
Z401 = "Z401",
};
/** @category Printer model library */
export interface PrinterModelMeta {
model: PrinterModel;
id: [number, ...number[]];
@ -81,6 +83,7 @@ export interface PrinterModelMeta {
densityDefault: number;
}
/** @category Printer model library */
export const modelsLibrary: PrinterModelMeta[] = [
{
model: PrinterModel.A20,
@ -722,10 +725,12 @@ export const modelsLibrary: PrinterModelMeta[] = [
},
];
/** @category Printer model library */
export const getPrinterMetaById = (id: number): PrinterModelMeta | undefined => {
return modelsLibrary.find((o) => o.id.includes(id));
};
/** @category Printer model library */
export const getPrinterMetaByModel = (model: PrinterModel): PrinterModelMeta | undefined => {
return modelsLibrary.find((o) => o.model === model);
};

@ -1,5 +1,6 @@
/**
* Utility class for various common operations.
* @category Helpers
*/
export class Utils {
/**
@ -149,6 +150,7 @@ export class Utils {
/**
* Utility class for validating objects.
* @category Helpers
*/
export class Validators {
/**

@ -1,13 +1,12 @@
{
"$schema": "https://typedoc.org/schema.json",
"name": "NiimBlueLib Docs",
"entryPoints": ["./src"],
"entryPoints": ["./src/index.ts"],
"navigationLinks": {
"GitHub": "https://github.com/MultiMote/niimbluelib"
},
"treatValidationWarningsAsErrors": true,
"excludeReferences": true,
"entryPointStrategy": "expand",
"out": "./docs/html",
"projectDocuments": ["docs/documents/*.md"],
}

@ -33,6 +33,7 @@ fetch("https://oss-print.niimbot.com/public_resources/static_resources/devices.j
console.log('import { PrintDirection } from "./image_encoder";');
console.log('import { LabelType as LT } from "./packets";\n');
console.log("/** @category Printer model library */");
console.log("export enum PrinterModel {");
console.log(' UNKNOWN = "UNKNOWN",');
for (const item of items) {
@ -43,6 +44,7 @@ fetch("https://oss-print.niimbot.com/public_resources/static_resources/devices.j
console.log("};");
console.log(`
/** @category Printer model library */
export interface PrinterModelMeta {
model: PrinterModel;
id: [number, ...number[]];
@ -56,6 +58,7 @@ export interface PrinterModelMeta {
}
`);
console.log("/** @category Printer model library */");
console.log("export const modelsLibrary: PrinterModelMeta[] = [");
for (const item of items) {
if (item.codes.length === 0) {
@ -83,10 +86,12 @@ export interface PrinterModelMeta {
console.log("];");
console.log(`
/** @category Printer model library */
export const getPrinterMetaById = (id: number): PrinterModelMeta | undefined => {
return modelsLibrary.find((o) => o.id.includes(id));
};
/** @category Printer model library */
export const getPrinterMetaByModel = (model: PrinterModel): PrinterModelMeta | undefined => {
return modelsLibrary.find((o) => o.model === model);
};`);