Skip to content

Commit

Permalink
Add more ZPL config commands (#58)
Browse files Browse the repository at this point in the history
ZPL has a lot more bells and whistles when it comes to configuring
printers, this adds more extension commands to support them.

This PR does a few things:

1. Adds support for language-specific configs. This enables tracking and
modifying settings unique to one language.
3. Adds the raw config and XML files from multiple printers in my
collection for ZPL analysis and testing.
4. Fixes several issues related to host ident on weird machines.
5. Adds a bunch more discovered ZPL config lines.
2. Adds ZPL operations.

* Graphing Sensor Calibration
* Set power up / head close action
* Set manual sensor calibration values
  • Loading branch information
Cellivar authored Dec 31, 2024
1 parent 466ed92 commit 879daa4
Show file tree
Hide file tree
Showing 56 changed files with 5,103 additions and 543 deletions.
115 changes: 88 additions & 27 deletions demo/advanced.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
// This is a utility lib we'll be using that makes it easier to use devices.
import * as WebDevices from 'web-device-mux';

// We'll drop these into the Window object so we can play with them in
// the DevTools console if we want to.
window.WebLabel = WebLabel;
window.WebDevices = WebDevices;

// For this demo we're going to make use of the USB printer manager
// so it can take care of concerns like the USB connect and disconnect events.

Expand Down Expand Up @@ -145,6 +150,7 @@

// The rest of this demo is an example of a basic label generator app.

// Get some bookkeeping out of the way..
// First we create an interface to describe our settings form.
interface ConfigModalForm extends HTMLCollection {
modalCancel : HTMLButtonElement
Expand All @@ -160,6 +166,39 @@
modalWithAutosense : HTMLInputElement
}

// A function to find and hide any alerts for a given alert ID.
function hideAlerts(alertId: string) {
const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? [];
existingAlerts.forEach((a: Element) => { a.remove(); });
}

// A function to make it easier to show alerts
function showAlert(
level: 'warning' | 'danger',
alertId: string,
titleHtml: string,
bodyHtml: string,
closedCallback = () => {}
) {
hideAlerts(alertId);

// Create the bootstrap alert div with the provided content
const alertWrapper = document.createElement('div');
alertWrapper.classList.add("alert", `alert-${level}`, "alert-dismissible", "fade", "show", alertId);
alertWrapper.id = alertId;
alertWrapper.role = "alert";
alertWrapper.innerHTML = `
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<h4>${titleHtml}</h4>
${bodyHtml}`;

// Add it to the document and activate it
document.getElementById('printerAlertSpace')?.appendChild(alertWrapper);
new bootstrap.Alert(alertWrapper);

alertWrapper.addEventListener('closed.bs.alert', closedCallback);
}

// The app's logic is wrapped in a class just for ease of reading.
class BasicLabelDesignerApp {
constructor(
Expand All @@ -186,31 +225,26 @@
// Printers themselves also have events, let's show an alert on errors.
const printer = detail.device;
printer.addEventListener('reportedError', ({ detail: msg }) => {
// Use the same ID so there's only one error message per printer.
const alertId = `alert-printererror-${printer.printerSerial}`;
// Hide any existing alerts for this printer
const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? [];
existingAlerts.forEach((a: Element) => bootstrap.Alert.getInstance(a)?.close());
hideAlerts(alertId);

// Error messages are also status messages, such as indicating no problem.
if (msg.errors.size === 0 || msg.errors.has(WebLabel.ErrorState.NoError)) { return; }

const alertWrapper = document.createElement('div');
alertWrapper.classList.add("alert", "alert-warning", "alert-dismissible", "fade", "show", alertId);
alertWrapper.id = alertId;
alertWrapper.role = "alert";
alertWrapper.innerHTML = `
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<h4>Printer <strong>${printer.printerSerial}</strong> has an error</h4>
<p><ul>${Array.from(msg.errors).map(e => `<li>${e}`)}</ul></p>
showAlert(
// Show a warning for this printer
'warning',
alertId,
`Printer <strong>${printer.printerSerial}</strong> has an error`,
// There can be multiple errors, just show their raw values. A better
// application would use these for good messages!
`<p><ul>${Array.from(msg.errors).map(e => `<li>${e}`)}</ul></p>
<hr>
<p>Fix the issue, then dismiss this alert to check the status again.</p>`;
document.getElementById('printerAlertSpace')?.appendChild(alertWrapper);
new bootstrap.Alert(alertWrapper);

// and even go one layer deeper!
alertWrapper.addEventListener('closed.bs.alert', () => {
printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument);
});
<p>Fix the issue, then dismiss this alert to check the status again.</p>`,
// And when the alert is dismissed, check the status again!
() => printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument)
);
});
});
this.manager.addEventListener('disconnectedDevice', () => {
Expand Down Expand Up @@ -598,7 +632,27 @@ <h4>Printer <strong>${printer.printerSerial}</strong> has an error</h4>
window.label_app = app;

// Now we'll fire the reconnect since our UI is wired up.
await printerMgr.forceReconnect();
try {
await printerMgr.forceReconnect();
} catch (e) {
if (e instanceof WebDevices.DriverAccessDeniedError) {
// This happens when the operating system didn't let Chrome connect.
// Usually either another tab is open talking to the device, or the driver
// is already loaded by another application.
showAlert(
'danger',
'alert-printer-comm-error',
`Operating system refused device access`,
`<p>This usually happens for one of these reasons:
<ul>
<li>Another browser tab is already connected.
<li>Another application loaded a driver to talk to the device.
<li>You're on Windows and need to replace the driver.
</ul>
Fix the issue and re-connect to the device.</p>`
);
}
}

// We're done here. Bring in the dancing lobsters.

Expand Down Expand Up @@ -698,16 +752,18 @@ <h5>Media Settings</h5>
<div class="col">
<label for="modalLabelWidth" class="form-label">Media Width</label>
<div class="input-group mb-3">
<input id="modalLabelWidth" type="text" class="form-control" placeholder="2.25"
aria-label="Width" required pattern="\d+\.*\d*">
<input id="modalLabelWidth" type="text" class="form-control" placeholder="1.23"
aria-label="Width" required pattern="\d+\.*\d*"
data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="top" title="">
<span id="modalLabelWidthText" class="input-group-text">in</span>
</div>
</div>
<div class="col">
<label for="modalLabelHeight" class="form-label">Media Length</label>
<div class="input-group mb-3">
<input id="modalLabelHeight" type="text" class="form-control" placeholder="2.25"
aria-label="Height" required pattern="\d+\.*\d*">
<input id="modalLabelHeight" type="text" class="form-control" placeholder="1.23"
aria-label="Height" required pattern="\d+\.*\d*"
data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="top" title="Auto-Calibrate will override this.">
<span id="modalLabelHeightText" class="input-group-text">in</span>
</div>
</div>
Expand All @@ -727,7 +783,8 @@ <h5>Media Settings</h5>
<label for="modalLabelOffsetLeft" class="form-label">Left Offset</label>
<div class="input-group mb-3">
<input id="modalLabelOffsetLeft" type="text" class="form-control" placeholder="0"
aria-label="Width" required pattern="-?\d+">
aria-label="Width" required pattern="-?\d+"
data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="right" title="Default is correct for configured width.">
<span class="input-group-text">dots</span>
</div>
</div>
Expand Down Expand Up @@ -760,15 +817,19 @@ <h5>Printer Settings</h5>
<label for="modalDarkness" class="col-sm-5 col-form-label">Darkness</label>
<div class="col-sm-7">
<div class="input-group col-sm-7">
<input type="number" min="1" max="99" id="modalDarkness" class="form-control" aria-label="Darkness"/>
<input type="number" min="1" max="99" id="modalDarkness" class="form-control" aria-label="Darkness"
data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="right" title="Use the lowest setting with consistent results.<br>Higher values wear the head faster."
/>
<span class="input-group-text">%</span>
</div>
</div>
</div>
<div class="col row mb-3">
<label for="modalSpeed" class="col-sm-5 col-form-label">Speed</label>
<div class="col-sm-7">
<select id="modalSpeed" class="form-select" aria-label="Speed">
<select id="modalSpeed" class="form-select" aria-label="Speed"
data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="right" title="Printing faster needs darker printing.<br>Lower darkness if you get sticking.<br>Test the combinations for best results."
>
<option value="3">1.5 ips</option>
<option value="4">2 ips</option>
<option value="5">2.5 ips</option>
Expand Down
9 changes: 9 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,17 @@
<!-- Here's some actual demo code. Note that it is typescript, not javascript. -->
<script type="text/typescript" id="cool_demo_code_to_show_off_the_lib">
// First import the lib!
// This is our lib in this repo here
import * as WebLabel from 'webzlp';

// This is a utility lib we'll be using that makes it easier to use devices.
import * as WebDevices from 'web-device-mux';

// We'll drop these into the Window object so we can play with them in
// the DevTools console if we want to.
window.WebLabel = WebLabel;
window.WebDevices = WebDevices;

// For this demo we're going to make use of the USB printer manager
// so it can take care of concerns like the USB connect and disconnect events.

Expand Down
89 changes: 69 additions & 20 deletions demo/test_advanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ printerMgr.addEventListener('disconnectedDevice', ({ detail }) => {

// The rest of this demo is an example of a basic label generator app.

// Get some bookkeeping out of the way..
// First we create an interface to describe our settings form.
interface ConfigModalForm extends HTMLCollection {
modalCancel : HTMLButtonElement
Expand All @@ -95,6 +96,39 @@ interface ConfigModalForm extends HTMLCollection {
modalWithAutosense : HTMLInputElement
}

// A function to find and hide any alerts for a given alert ID.
function hideAlerts(alertId: string) {
const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? [];
existingAlerts.forEach((a: Element) => { a.remove(); });
}

// A function to make it easier to show alerts
function showAlert(
level: 'warning' | 'danger',
alertId: string,
titleHtml: string,
bodyHtml: string,
closedCallback = () => {}
) {
hideAlerts(alertId);

// Create the bootstrap alert div with the provided content
const alertWrapper = document.createElement('div');
alertWrapper.classList.add("alert", `alert-${level}`, "alert-dismissible", "fade", "show", alertId);
alertWrapper.id = alertId;
alertWrapper.role = "alert";
alertWrapper.innerHTML = `
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<h4>${titleHtml}</h4>
${bodyHtml}`;

// Add it to the document and activate it
document.getElementById('printerAlertSpace')?.appendChild(alertWrapper);
new bootstrap.Alert(alertWrapper);

alertWrapper.addEventListener('closed.bs.alert', closedCallback);
}

// The app's logic is wrapped in a class just for ease of reading.
class BasicLabelDesignerApp {
constructor(
Expand All @@ -121,31 +155,26 @@ class BasicLabelDesignerApp {
// Printers themselves also have events, let's show an alert on errors.
const printer = detail.device;
printer.addEventListener('reportedError', ({ detail: msg }) => {
// Use the same ID so there's only one error message per printer.
const alertId = `alert-printererror-${printer.printerSerial}`;
// Hide any existing alerts for this printer
const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? [];
existingAlerts.forEach((a: Element) => bootstrap.Alert.getInstance(a)?.close());
hideAlerts(alertId);

// Error messages are also status messages, such as indicating no problem.
if (msg.errors.size === 0 || msg.errors.has(WebLabel.ErrorState.NoError)) { return; }

const alertWrapper = document.createElement('div');
alertWrapper.classList.add("alert", "alert-warning", "alert-dismissible", "fade", "show", alertId);
alertWrapper.id = alertId;
alertWrapper.role = "alert";
alertWrapper.innerHTML = `
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<h4>Printer <strong>${printer.printerSerial}</strong> has an error</h4>
<p><ul>${Array.from(msg.errors).map(e => `<li>${e}`)}</ul></p>
showAlert(
// Show a warning for this printer
'warning',
alertId,
`Printer <strong>${printer.printerSerial}</strong> has an error`,
// There can be multiple errors, just show their raw values. A better
// application would use these for good messages!
`<p><ul>${Array.from(msg.errors).map(e => `<li>${e}`)}</ul></p>
<hr>
<p>Fix the issue, then dismiss this alert to check the status again.</p>`;
document.getElementById('printerAlertSpace')?.appendChild(alertWrapper);
new bootstrap.Alert(alertWrapper);

// and even go one layer deeper!
alertWrapper.addEventListener('closed.bs.alert', () => {
printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument);
});
<p>Fix the issue, then dismiss this alert to check the status again.</p>`,
// And when the alert is dismissed, check the status again!
() => printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument)
);
});
});
this.manager.addEventListener('disconnectedDevice', () => {
Expand Down Expand Up @@ -533,6 +562,26 @@ declare global {
window.label_app = app;

// Now we'll fire the reconnect since our UI is wired up.
await printerMgr.forceReconnect();
try {
await printerMgr.forceReconnect();
} catch (e) {
if (e instanceof WebDevices.DriverAccessDeniedError) {
// This happens when the operating system didn't let Chrome connect.
// Usually either another tab is open talking to the device, or the driver
// is already loaded by another application.
showAlert(
'danger',
'alert-printer-comm-error',
`Operating system refused device access`,
`<p>This usually happens for one of these reasons:
<ul>
<li>Another browser tab is already connected.
<li>Another application loaded a driver to talk to the device.
<li>You're on Windows and need to replace the driver.
</ul>
Fix the issue and re-connect to the device.</p>`
);
}
}

// We're done here. Bring in the dancing lobsters.
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.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@
"vitest": "^2.1.6"
},
"dependencies": {
"web-device-mux": "^0.4.1"
"web-device-mux": "^0.5.0"
}
}
Loading

0 comments on commit 879daa4

Please sign in to comment.