Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ web/tmp/**
__pycache__/**
__pycache__

# Remove test scripts
# Remove test scripts and test files
tests/**
tests
tests
barcodes.bin
receipt-with-logo.bin
receipt-with-qrcode.bin

# Remove pybabel config and translation scripts from the runtime container
babel.cfg
*_babel_*.sh
108 changes: 96 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,119 @@ The printer emulates a 80mm roll of paper.
## Limits
This docker image is not to be exposed on a public network (see [known issues](#known-issues))

A print cannot last longer than 10 seconds. This timeout could be changed at some point, or made configurable.
A print cannot last longer than 10 seconds. This timeout could be changed in the code, or made configurable at some later point.

## Quick start

This project requires:
- A Docker installation (kubernetes should work, but is untested.)

To install v3.0 from source:
### Use the prebuilt container

A prebuilt container of this project is avaliable on [Docker Hub](https://hub.docker.com/repository/docker/gilbertfl/escpos-netprinter).

To run the prebuilt container:
```bash
wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/tags/3.0.zip
unzip 3.0.zip
cd escpos-netprinter-3.0
docker build -t escpos-netprinter:3.0 .
docker run -d \
-p 515:515/tcp \
-p 80:80/tcp \
-p 9100:9100/tcp \
--mount source=receiptVolume,target=/home/escpos-emu/web \
gilbertfl/escpos-netprinter:3.2
```
### Once started
Once started, the container will accept prints by JetDirect on the default port(9100) and by lpd on the default port(515). You can access all received receipts with the web application at port 80.

The receipts are kept on a docker volume, so they will be kept if the container is restarted. To make the prints temporary, simply remove the `--mount` line from the run command.

Version 3.2 is capable of dealing with all status requests from POS systems as described in the Epson APG.

## Working on the code

### Building from source

To install v3.2 from source:

```bash
wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/tags/3.2.zip
unzip 3.2.zip
cd escpos-netprinter-3.2
docker build -t escpos-netprinter:3.2 .
```

To run the resulting container:
```bash
docker run -d --rm --name escpos_netprinter -p 515:515/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:3.0
docker run -d \
-p 515:515/tcp \
-p 80:80/tcp \
-p 9100:9100/tcp \
--mount source=receiptVolume,target=/home/escpos-emu/web \
escpos-netprinter:3.2
```

### Debugging
Two interfaces have been made avaliable to help debugging.

There is also a general environment flag that make the Docker logs verbose: set ```ESCPOS_DEBUG=True``` (case-sensitive)
```bash
docker run -d \
-p 515:515/tcp \
-p 80:80/tcp \
-p 9100:9100/tcp \
--mount source=receiptVolume,target=/home/escpos-emu/web \
--env ESCPOS_DEBUG=True \
escpos-netprinter:3.2
```
Setting this variable will generate logs from 3 different sources:
- The CUPS printer driver
- Jetdirect requests
- The ESC/POS to HTML conversion itself
- Browsing the web interface

If you have problems with the CUPS interface, you can add port 631 to access the CUPS administrator interface. The CUPS administrative username is `cupsadmin` and the password is `123456`; you can change that in the dockerfile or at runtime inside the administrator interface.
```bash
docker run -d \
-p 515:515/tcp \
-p 80:80/tcp \
-p 9100:9100/tcp \
-p 631:631/tcp
--mount source=receiptVolume,target=/home/escpos-emu/web \
escpos-netprinter:3.2
```
It should now accept prints by JetDirect on the default port(9100) and by lpd on the default port(515), and you can visualize it with the web application at port 80.
For debugging, you can add port 631 to access the CUPS interface. The CUPS administrative username is `cupsadmin` and the password is `123456`.

As of version 2.0, this has been tested to work with a regular POS program without adapting it.
### Runtime Directory Structure

The following directories inside the container are useful:
- `/home/escpos-emu/web/`: Stores all the printed receipts and printer state (including logs)
- `/home/escpos-emu/web/receipts`: Stores the HTML receipts
- `/home/escpos-emu/web/tmp`: Stores temporary files during processing (for debugging only)
- `/home/escpos-emu/web/receipt_list.csv`: Created at runtime, this file contains the list of the printed receipts with the file location.

## Translations

This project supports internationalization using [Flask-Babel](https://python-babel.github.io/flask-babel). All translation files are in the `/translations` subdirectory, including the `messages.pot` file.

You can create a new locale using the following scripts:
- `init_babel_locale.sh`: to create a new locale file NOTE: you will need to add the locale in `babel_config.py` manually after this.
- `compile_babel_translations.sh`: to compile all locales and make them available to Flask. This should be launched after every change to the translations, not immediately after creating a locale.

Other helpful scripts for developers :
- `update_babel_messages.sh`: If you add more translatable text in the UI or code, use this script to make Babel extract and add them to the locale files
- `extract_babel_messages.sh`: If you ever want to generate the `messages.pot` file without updating any locale, this is the script.

## Configuration Options

Version 3.0 is now dealing with all status requests from POS systems as described in the Epson APG.
The following environment variables can be configured:
| Variable | Default | Description |
|----------|---------|-------------|
| ESCPOS_DEBUG | false | Enable debug mode for detailed logs |
| PRINTER_PORT | 9100 | JetDirect port for printer communication |
| FLASK_RUN_DEBUG | false | Enable Flask debug mode |
| FLASK_RUN_PORT | 80 | Sets the listening port for the web interface |
| ESCPOS_TIMEZONE | "America/Montreal" | Sets the timezone for all datetime formatting |

## Known issues
While version 3.0 is no longer a beta version, it has known defects:
While version 3.2 is ready for production, it has known defects:
- It still uses the Flask development server, so it is unsafe for public networks.
- While it works with simple drivers, for example the one for the MUNBYN ITPP047 printers, the [Epson utilities](https://download.epson-biz.com/modules/pos/) refuse to speak to it.

2 changes: 2 additions & 0 deletions babel.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[python: ./*.py]
[jinja2: templates/**.html.j2]
4 changes: 4 additions & 0 deletions babel_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This is the flask-babel runtime config file

class Config:
LANGUAGES = ['fr', 'en']
21 changes: 21 additions & 0 deletions barcodes.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@
.ta!8Code128:
Hh-kI012z !.,
.ta!8Code93:
Hh-kH012.ABZ
.ta!8Codabar:
Hh-kG A012$+-./:A
.ta!8Code39:
Hh-kEABCZ012
.ta!8Ean8:
Hh-kD0123456
.ta!8Ean13:
Hh-kC 012345678901
.ta!8UpcE:
Hh-kB1234567
.ta!8UpcA:
Hh-kA 036000291452
.ta!8ITF:
Hh-kF
0123456789
d
4 changes: 4 additions & 0 deletions compile_babel_translations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
# This script compiles all locales and makes them usable by Python.

pybabel compile -d translations
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"squizlabs/php_codesniffer" : "^2.8",
"mike42/escpos-php": "^1.5",
"chillerlan/php-qrcode": "^5.0.3",
"zbateson/mb-wrapper": "2.0.1"
"zbateson/mb-wrapper": "2.0.1",
"picqer/php-barcode-generator": "^3.2"
},
"autoload" : {
"psr-4" : {
Expand Down
18 changes: 18 additions & 0 deletions doc/esc2html.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ This utility is included with escpos-tools. See the

## Usage

Basic usage:
```
php esc2html FILE
```

Optional debugging:
```
php esc2html --debug FILE
```
In the debug mode, the debugging messages are sent to the debug console while the output is the same as in basic mode.


## Example

### Basic example
```
$ php esc2html.php receipt-with-logo.bin > receipt-with-logo.html
```
Expand All @@ -38,6 +47,15 @@ $ cat receipt-with-logo.bin > /dev/usb/lp0

The input file used as an example here was generated by [escpos-php](https://github.com/mike42/escpos-php), and is available [here](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/receipt-with-logo.bin).

### Saving debugging output separately from the real output

```
$ php esc2html.php receipt-with-qrcode.bin 1>test.html 2>log.txt
```

The HTML representation of the receipt is saved to test.html, and the debugging output to log.txt.


## Further conversions

This utility will create a formatted HTML file. This can be converted accurately to PDF
Expand Down
6 changes: 6 additions & 0 deletions dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RUN install-php-extensions mbstring @composer imagick
#Install Flask
RUN apt-get update
RUN apt-get install -y python3-flask
RUN apt-get install -y python3-flask-babel
RUN apt-get install -y python3-lxml

#Install CUPS
Expand Down Expand Up @@ -54,6 +55,11 @@ ENV FLASK_RUN_DEBUG=false
# To activate the netprinter debug mode, set at True (case-sensitive)
ENV ESCPOS_DEBUG=false

#Localization support environment variables
ENV ESCPOS_TIMEZONE="America/Montreal"


# Expose all necessary ports
EXPOSE ${PRINTER_PORT}
EXPOSE ${FLASK_RUN_PORT}
#Expose the lpd port
Expand Down
81 changes: 77 additions & 4 deletions esc2html.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
exit(1);
}
else {
if ($argv[1]=='--debug'){
if ($argv[1]=='--debug'){
$debugMode = true;
if (!isset($argv[2])) {
print("Usage: php " . $argv[0] . " [--debug] filename ". $argc-1 . " arguments received\n");
Expand Down Expand Up @@ -46,7 +46,7 @@
if ( !$fp ) {
error_log("File ". $targetFilename . "not found.");
exit(1);
}
}

$parser = new Parser();
$parser -> addFile($fp);
Expand All @@ -60,6 +60,9 @@
$imgNo = 0;
$skipLineBreak = false;
$code2dStorage = new Code2DStateStorage();
$barcodeHeight = null;
$barcodeWidth = null;
$barcodeHri = null;

foreach ($commands as $cmd) {
if ($debugMode) error_log("". get_class($cmd) ."", 0); //Output the command class in the debug console
Expand Down Expand Up @@ -121,7 +124,7 @@
error_log("Data size:". $sub->getDataSize() ."",0);
error_log("Data: " . $sub->get_data() ."",0);
}
if($sub->isAvailableAs('QRCodeSubCommand')){
if($sub->isAvailableAs('QRCodeSubCommand')){
switch ($sub->get_fn()) {
case 65: //set model
$code2dStorage->setQRModel($sub->get_data());
Expand Down Expand Up @@ -158,13 +161,83 @@
$qrcodeData = $code2dStorage->getQRCodeData();
$outp[] = "<div class=\"esc-line esc-justify-center\"><img class=\"esc-bitimage\" src=\"$qrcodeURI\" alt=\"$qrcodeData\" /></div>";
}

break;
case 82: //Transmit size information of symbol storage data.
# TODO: maybe implement by printing the info?
break;
}
}
}if ($cmd -> isAvailableAs('SetBarcodeHeightCmd')) {
$barcodeHeight = $cmd -> getHeight();
} else if ($cmd -> isAvailableAs('SetBarcodeWidthCmd')) {
$barcodeWidth = $cmd -> getWidth();
} else if ($cmd -> isAvailableAs('SelectBarCodeHriCmd')) {
$barcodeHri = $cmd -> getHRI();
} else if ($cmd -> isAvailableAs('PrintBarcodeCmd')) {
$types = [ // download4.epson.biz/sec_pubs/pos/reference_en/escpos/gs_lk.html
0 => 'TypeUpcA',
65 => 'TypeUpcA',
1 => 'TypeUpcE',
66 => 'TypeUpcE',
2 => 'TypeEan13',
67 => 'TypeEan13',
3 => 'TypeEan8',
68 => 'TypeEan8',
4 => 'TypeCode39',
69 => 'TypeCode39',
6 => 'TypeCodabar',
71 => 'TypeCodabar',
72 => 'TypeCode93',
73 => 'TypeCode128',
5 => 'NoTypePreview',
70 => 'NoTypePreview',
74 => 'NoTypePreview',
75 => 'NoTypePreview',
76 => 'NoTypePreview',
77 => 'NoTypePreview',
78 => 'NoTypePreview',
79 => 'NoTypePreview',
];
$type = $types[$cmd->getType()] ?? null;
$data = $cmd -> subCommand()->getData();
$classes = getBlockClasses($formatting);
$classesStr = implode(" ", $classes);
if ($type){ //A valid barcode system type has been specified
if($type !== 'NoTypePreview'){
$renderer = new \Picqer\Barcode\Renderers\PngRenderer();
$renderer->setBackgroundColor([255, 255, 255]);
if (!class_exists(\Imagick::class)) {
$renderer->useGd();
}
else {
$renderer->useImagick();
}
$type = '\\Picqer\\Barcode\\Types\\' . $type;
$barcode = (new $type)->getBarcode($data);
$imgSrc = base64_encode($renderer->render($barcode, $barcodeWidth ?? $barcode->getWidth(), $barcodeHeight ?? 40));
$lineHtml = "<img class=\"esc-bitimage\" src=\"data:image/jpeg;base64,{$imgSrc}\" alt=\"{$data}\" />";
}
else { // A valid type that cannot be rendered by the library.
$classesStr .= ' esc-line-command';
$lineHtml = "<span class=\"command\">BARCODE {$cmd->getType()} (NO PREVIEW AVAILABLE)" . (in_array($barcodeHri, [1, 2, 3]) ? '' : " [$data]") . "</span>";
}
}
else { // Missing or invalid barcode system type
$classesStr .= ' esc-line-command';
$lineHtml = "<span class=\"command\">BARCODE {$cmd->getType()} (UNKNOWN TYPE)" . (in_array($barcodeHri, [1, 2, 3]) ? '' : " [$data]") . "</span>";
$barcodeWidth = $barcodeHeight = $barcodeHri = null;
continue;
}
if (in_array($barcodeHri, [1, 3])) {
$lineHtml = "<div>$data</div>" . $lineHtml;
}
if (in_array($barcodeHri, [2, 3])) {
$lineHtml = $lineHtml . "<div>$data</div>";
}
$outp[] = wrapInline("<div class=\"$classesStr\">", "</div>", wrapInline("<span class=\"esc-justify-center\">", "</span>", $lineHtml));
$lineHtml = ""; // flush buffer
$barcodeWidth = $barcodeHeight = $barcodeHri = null;
}
}

Expand Down
Loading