Skip to content

Commit

Permalink
Feature/add adapters (#10)
Browse files Browse the repository at this point in the history
* add adapters

* enable test on push
  • Loading branch information
beuluis authored Jul 10, 2023
1 parent bbad77f commit 72b4173
Show file tree
Hide file tree
Showing 12 changed files with 606 additions and 499 deletions.
2 changes: 1 addition & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

#npm run test:cov
npm run test:cov
npx hook-cli checkPackageVersion
npx hook-cli checkForFileChanged CHANGELOG.md
npx hook-cli checkForVulnerabilities
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 2023-07-10 - 0.1.0

- Add adapter concept
- rename from `@beuluis/thermal-mqttastic` to `@beuluis/thermaltastic`

## 2023-07-10 - 0.0.3

- Update readme
Expand Down
78 changes: 53 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
<!-- PROJECT HEADER -->
<br />
<p align="center">
<h3 align="center">ThermalMqttastic</h3>
<h3 align="center">Thermaltastic</h3>

<p align="center">
<img align="center" src="./images/print.jpg" alt="drawing" width="170"/>
<br />
<br />
Control a Adafruit thermal printer over mqtt. This library is very WIP.
Control a Adafruit thermal printer over different adapters. This library is very WIP.
<br />
<br />
·
<a href="https://github.com/beuluis/ThermalMqttastic/issues">Report Bug</a>
<a href="https://github.com/beuluis/Thermaltastic/issues">Report Bug</a>
·
<a href="https://github.com/beuluis/ThermalMqttastic/issues">Request Feature</a>
<a href="https://github.com/beuluis/Thermaltastic/issues">Request Feature</a>
·
</p>
</p>
Expand All @@ -31,45 +31,77 @@ I wanted to talk to a thermal printer over an api. I experimented with a esp32 a

After investigating the [Adafruit library](https://github.com/adafruit/Adafruit-Thermal-Printer-Library/tree/master) and many many failed other attempts, I concluded that I can extract the heavy lifting to TypeScript and only run a light mqtt to serial implementation on the esp32.

To allow different 'streams' like mqtt I came up with the adapter concept.

So now you can utilize the versatile package landscape of NPM to generate bitmaps, wrap it in REST APIs and and and.

## Installation

```bash
npm i @beuluis/thermal-mqttastic
npm i @beuluis/thermaltastic
```

### Unstable installation

The `next` dist-tag is kept in sync with the latest commit on main. So this contains always the latest changes but is highly unstable.

```bash
npm i @beuluis/thermal-mqttastic@next
npm i @beuluis/thermaltastic@next
```

## Usage

```typescript
const printer = new Thermaltastic(adapter);

await printer.begin();

await printer.println('Hello World!');
```

## Adapters

The original library used a serial stream to send the bytes to the printer. In this implementation we use adapters to achieve this.

A adapter defines how the printer receives the bytes.

### MqttasticAdapter

Send the to print bytes over mqtt.

> :warning: **You need the corresponding arduino MQTT client also listening**: See [ThermalMqttasticPrinter](https://registry.platformio.org/libraries/beuluis/ThermalMqttasticPrinter) for more details.
You also need a MQTT broker. An example would be [eclipse-mosquitto](https://hub.docker.com/_/eclipse-mosquitto).

```typescript
const printer = new ThermalMqttastic({
const adapter = new MqttasticAdapter({
mqttUrl: 'mqtt://localhost:1883',
mqttOptions: {
password: '12345678',
},
});

await printer.begin();

await printer.println('Hello World!');
new Thermaltastic(adapter);
```

### MQTT connection
#### MQTT connection

`mqttOptions` is the option interface of the [MQTT](https://www.npmjs.com/package/mqtt) package. Please refer to this documentation on how to establish the connection.

### Implement your own adapter

For your own adapter you just need to implement the `Adapter` interface.

```typescript
export class MyAdapter implements Adapter {
public async begin() {}

public async write(...bytes: [number, number?, number?, number?]) {}

public async writeBytes(...bytes: [number, number?, number?, number?]) {}
}
```

## Functions

Parameters get validated using [zod](https://www.npmjs.com/package/zod). Please refer to this documentation on how the parameters get validated.
Expand Down Expand Up @@ -566,11 +598,7 @@ await printer.setCharSpacing(10);
You can enable the debugging logger when you provide a logger to the constructor.

```typescript
const printer = new ThermalMqttastic({
mqttUrl: 'mqtt://localhost:1883',
mqttOptions: {
password: '12345678',
},
const printer = new Thermaltastic(adapter, {
logger: console,
});
```
Expand All @@ -596,12 +624,12 @@ Luis Beu - [email protected]
<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->

[contributors-shield]: https://img.shields.io/github/contributors/beuluis/ThermalMqttastic.svg?style=flat-square
[contributors-url]: https://github.com/beuluis/ThermalMqttastic/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/beuluis/ThermalMqttastic.svg?style=flat-square
[forks-url]: https://github.com/beuluis/ThermalMqttastic/network/members
[stars-shield]: https://img.shields.io/github/stars/beuluis/ThermalMqttastic.svg?style=flat-square
[stars-url]: https://github.com/beuluis/ThermalMqttastic/stargazers
[issues-shield]: https://img.shields.io/github/issues/beuluis/ThermalMqttastic.svg?style=flat-square
[issues-url]: https://github.com/beuluis/ThermalMqttastic/issues
[license-shield]: https://img.shields.io/github/license/beuluis/ThermalMqttastic.svg?style=flat-square
[contributors-shield]: https://img.shields.io/github/contributors/beuluis/Thermaltastic.svg?style=flat-square
[contributors-url]: https://github.com/beuluis/Thermaltastic/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/beuluis/Thermaltastic.svg?style=flat-square
[forks-url]: https://github.com/beuluis/Thermaltastic/network/members
[stars-shield]: https://img.shields.io/github/stars/beuluis/Thermaltastic.svg?style=flat-square
[stars-url]: https://github.com/beuluis/Thermaltastic/stargazers
[issues-shield]: https://img.shields.io/github/issues/beuluis/Thermaltastic.svg?style=flat-square
[issues-url]: https://github.com/beuluis/Thermaltastic/issues
[license-shield]: https://img.shields.io/github/license/beuluis/Thermaltastic.svg?style=flat-square
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@beuluis/thermal-mqttastic",
"version": "0.0.3",
"description": "Wrapper for my adafruit thermal printer api",
"name": "@beuluis/thermaltastic",
"version": "0.1.0",
"description": "Control a Adafruit thermal printer over different adapters",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
Expand All @@ -21,7 +21,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/beuluis/ThermalMqttastic.git"
"url": "git+https://github.com/beuluis/Thermaltastic.git"
},
"keywords": [
"thermal",
Expand All @@ -32,9 +32,9 @@
"author": "Luis Beu <[email protected]> (https://luisbeu.de/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/beuluis/ThermalMqttastic/issues"
"url": "https://github.com/beuluis/Thermaltastic/issues"
},
"homepage": "https://github.com/beuluis/ThermalMqttastic#readme",
"homepage": "https://github.com/beuluis/Thermaltastic#readme",
"prettier": "@beuluis/prettier-config",
"lint-staged": {
"*.md": [
Expand Down
57 changes: 21 additions & 36 deletions src/ThermalMqttastic.ts → src/Thermaltastic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
* MIT license, all text above must be included in any redistribution.
*/

import type { IClientOptions, AsyncMqttClient } from 'async-mqtt';
import { connectAsync } from 'async-mqtt';
import { z } from 'zod';
import type { Barcode } from './enums';
import { AsciiCode, CharacterCommands } from './enums';
Expand Down Expand Up @@ -60,7 +58,12 @@ const BAUDRATE = 19_200; // How many bits per second the serial port should tran
const BYTE_TIME = Math.ceil((11 * 1_000_000 + BAUDRATE / 2) / BAUDRATE);

// TODO: add multi client support with start/stop
// TODO: Extract MQTT connector and write class interface that can be used
export interface Adapter {
begin: () => Promise<void>;
write: (...bytes: [number, number?, number?, number?]) => Promise<void>;
writeBytes: (...bytes: [number, number?, number?, number?]) => Promise<void>;
}

export interface Logger {
log?: (message: string) => void;
table?: (table: object) => void;
Expand All @@ -69,11 +72,9 @@ export interface Logger {
export interface InitOptions {
additionalStackTimeout?: number;
logger?: Logger;
mqttOptions?: IClientOptions;
mqttUrl: string;
}

export class ThermalMqttastic {
export class Thermaltastic {
protected resumeTime = 0;

protected prevByte = 0;
Expand All @@ -99,22 +100,17 @@ export class ThermalMqttastic {
protected printMode = 0;

// Added class properties
protected mqttClient?: AsyncMqttClient;

protected mqttUrl: string;

protected mqttOptions?: IClientOptions;
protected adapter: Adapter;

protected logger?: Logger;

protected additionalStackTimeout: number;

// Constructor
public constructor(initOptions: InitOptions) {
this.additionalStackTimeout = initOptions.additionalStackTimeout ?? 5;
this.logger = initOptions.logger;
this.mqttUrl = initOptions.mqttUrl;
this.mqttOptions = initOptions.mqttOptions;
public constructor(adapter: Adapter, initOptions?: InitOptions) {
this.adapter = adapter;
this.additionalStackTimeout = initOptions?.additionalStackTimeout ?? 5;
this.logger = initOptions?.logger;
}

// Additional functions
Expand All @@ -135,14 +131,6 @@ export class ThermalMqttastic {
// additionalStackTimeout only gets added the modifier only effects the original BYTE_TIME
return BYTE_TIME * modifier + this.additionalStackTimeout * 1_000_000; // additionalStackTimeout is in ms so we convert it to nanoseconds
}

protected async publish(topic: string, payload: string) {
if (!this.mqttClient) {
throw new Error('MQTT client is not initialized. Did you call begin()?');
}

await this.mqttClient.publish(topic, payload, { qos: 2 });
}
// Additional functions end

// This method sets the estimated completion time for a just-issued task.
Expand Down Expand Up @@ -204,11 +192,9 @@ export class ThermalMqttastic {
protected async writeBytes(...bytes: [number, number?, number?, number?]) {
this.mayLog('writeBytes called');

const payloadString = bytes.join(',');

await this.timeoutWait();
await this.publish('writeBytes', payloadString);
this.mayLog(`Written ${payloadString} to stream (writeBytes)`);
await this.adapter.writeBytes(...bytes);
this.mayLog(`Written ${bytes.join(',')} to stream (writeBytes)`);

this.timeoutSet(this.getByteTime(bytes.length));
}
Expand Down Expand Up @@ -249,12 +235,11 @@ export class ThermalMqttastic {
let temporaryByteTime = 0;

const sendPayload = async () => {
const payloadString = payload.join(',');

await this.timeoutWait();
await this.publish('write', payloadString);
const [first, ...rest] = payload;
await this.adapter.writeBytes(first, ...rest);

this.mayLog(`Written ${payloadString} to stream (write)`);
this.mayLog(`Written ${payload.join(',')} to stream (write)`);

this.timeoutSet(this.getByteTime(payload.length) + temporaryByteTime);

Expand Down Expand Up @@ -319,17 +304,17 @@ export class ThermalMqttastic {
z.number().int().nonnegative().parse(firmware);

// Wait for mqtt connection
this.mayLog('Connecting to MQTT');
this.mqttClient = await connectAsync(this.mqttUrl, this.mqttOptions);
this.mayLog('MQTT connection successful');
this.mayLog('Begin adapter');
await this.adapter.begin();
this.mayLog('Begin adapter successful');

this.firmware = firmware;
this.mayTable({ firmware });

// The printer can't start receiving data immediately upon power up --
// it needs a moment to cold boot and initialize. Allow at least 2
// sec of uptime before printer can receive data.
// TODO: replace with ready mqtt communication
// TODO: replace with ready communication
this.timeoutSet(2_000_000);

await this.wake();
Expand Down
46 changes: 46 additions & 0 deletions src/adapters/MqttasticAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* eslint-disable canonical/filename-match-regex */
import type { AsyncMqttClient, IClientOptions } from 'async-mqtt';
import { connectAsync } from 'async-mqtt';
import type { Adapter } from '../Thermaltastic';

export interface MqttasticAdapterInitOptions {
mqttOptions?: IClientOptions;
mqttUrl: string;
}

export class MqttasticAdapter implements Adapter {
public async begin() {
this.mqttClient = await connectAsync(this.mqttUrl, this.mqttOptions);
}

public constructor(initOptions: MqttasticAdapterInitOptions) {
this.mqttUrl = initOptions.mqttUrl;
this.mqttOptions = initOptions.mqttOptions;
}

protected mqttClient?: AsyncMqttClient;

protected mqttOptions?: IClientOptions;

protected mqttUrl: string;

protected async publish(topic: string, payload: string) {
if (!this.mqttClient) {
throw new Error('MQTT client is not initialized. Did you call begin()?');
}

await this.mqttClient.publish(topic, payload, { qos: 2 });
}

public async write(...bytes: [number, number?, number?, number?]) {
const payloadString = bytes.join(',');

await this.publish('write', payloadString);
}

public async writeBytes(...bytes: [number, number?, number?, number?]) {
const payloadString = bytes.join(',');

await this.publish('writeBytes', payloadString);
}
}
1 change: 1 addition & 0 deletions src/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MqttasticAdapter';
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './ThermalMqttastic';
export * from './adapters';
export * from './Thermaltastic';
export * from './enums';
Loading

1 comment on commit 72b4173

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 99.37% 472/475
🟢 Branches 96.08% 98/102
🟢 Functions 100% 75/75
🟢 Lines 99.36% 464/467

Test suite run success

124 tests passing in 4 suites.

Report generated by 🧪jest coverage report action from 72b4173

Please sign in to comment.