The code for this project is available from the ace4-labels project.
I’m planning to reorganise my collection of dance books now that I have received a ginormous number of additional books from another dance teacher who retired (thank you Meinhard!!). In particular, I want to sort the books by author/publisher rather than by title, to make it easier to find all the books by a particular deviser.
To facilitate this I would like to label each book with a “shelf code”
based on its author/publisher and title. For example, the shelf code
for Reel Friends by Ann Dix could be DIXA-RF01
. That way I can
keep my dance books reproducibly sorted by referring to the shelf
code, and the shelf code can also be stored in the collection on the
SCDDB, so a book can be easily found from its listing in the database.
Of course it would be trivial to label the books by hand, but the nerdy thing to do is to buy a label printer just so everything will be looking nice and neat. The label printer in question is a Phomemo M120, which is quite cheap (I got mine off Amazon for €40) and prints on rolls of 40mm×30mm self-adhesive labels (among other media). This is a thermal printer similar to (but better-quality than) the ones seen at supermarket checkouts, so I wouldn’t want to leave a label in the sun for a long time, but having it sit on a shelf should be OK; the advantage is that it doesn’t need ink.
The printer is marketed as an accessory for Android and iOS phones/tablets and comes with apps for these only – there are no drivers for Windows, Macs or Linux. It uses Bluetooth to communicate, and clever people with packet sniffers have found out what to send to the printer to make it print labels, so even if the printer doesn’t officially support Linux, using it from Linux is not a problem. The first section of this article describes how I do this.
What we really want, though, is to print labels from the SCDDB web pages, particularly the “Publications” tab of a dance collection detail page. This is in fact possible, if a little involved, and the second section of this article explains what one needs to do.
The Phomemo M120 has a resolution of 80 dots per centimetre, so a 40mm×30mm label consists of 240 rows of 320 pixels each. This is not huge but adequate for what we need to do. We want the following information to appear on a label:
(The QR code serves no direct purpose but it looks cool, and it actually works even though it is quite tiny. On the detail page there could be an “Add to current collection” button, similar to the “Add to list” button on dance detail pages, that would add the publication in question to the currently-active dance collection. OTOH, to have a label with a QR code the publication must have been part of some collection already, so having a convenient way of adding it to a collection may not actually be needed. Where this would come in useful would be when publishers print the QR code on their actual books, so it would be easy to add them to a collection after one buys them. Sigh.)
There are many ways of making graphics that could serve as labels, but the interesting challenge is how to get them to the printer. There is experimental CUPS support for the M120, but it is based on the raster backend mechanism that has been deprecated in CUPS for a while, so the easiest way is to simply assemble a bitmap “by hand” and push it to the printer framed by the appropriate control character sequences.
We use a Python program called m120-print
which supports a tiny
language used to describe label images. The following commands are
defined:
label W H
W
pixels wide and H
pixels high
(where W
and H
are literal integers). For now W
must be
320 and H
must be 240, but this is not enforced.font F S
F
at size S
. F
must be the name of a font
in the fonts
section of the configuration file (which basically
consists of a dictionary mapping font names to font file names).
S
must be an integer. By default, we use the B612
font,
which is a highly legible font designed by the Airbus corporation
for use on aircraft instrument panels. For convenience, if F
is .
(a period), the font name remains unchanged and only the size
changes to S
.text X Y T
T
, in the currently selected font,
starting at position (X
, Y
). (Note that Y
refers to the
baseline of the characters, not the top.) Text is output flush left
and does not wrap. Stuff beyond the right edge of the label is
simply ignored.textr X Y T
text
, except that it renders the text flush-right, i.e., the
X
value describes where the right end of the text baseline sits.qrcode X Y T
T
, with its top left
corner sitting at (X
, Y
).print
The “source code” for the sample label above would therefore look like
label 320 240
font B612-Regular 32
text 10 38 Ann Dix
text 10 80 Reel Friends
font . 45
textr 316 116 1001
font B612-Mono 64
text 10 170 DIXA
text 10 228 RF01
qrcode 211 129 https://my.strathspey.org/dd/publication/1001/
print
Once we have the bitmap for a label, all that remains is to send it to
the printer. This means that you need to connect the printer to your
Linux system first. We assume that your Linux PC does support
Bluetooth (not an issue on modern laptops) and that you have the BlueZ
package installed (bluez
on Debian GNU/Linux). In that case you can
make the printer available on a Bluetooth-based virtual serial device
as follows: Switch the printer on and, in a Linux console, issue the
hcitool scan
command. The computer outputs Scanning ...
and after
a little while a line like the following should appear.
$ hcitool scan
Scanning ...
DC:0D:30:C5:63:31 Q193G28O0880165
The first item on the line is the printer’s MAC address, while the
second item should match what is shown in the printer’s display. Armed
with this information, you can connect to the printer using the
rfcomm
command:
$ sudo rfcomm connect 0 DC:0D:30:C5:63:31
Connected /dev/rfcomm0 to DC:0D:30:C5:63:31 on channel 1
Press Ctrl-C for hangup
After this, the printer is ready for use. The default device name as
far as the m120-print
command is concerned, is /dev/rfcomm0
; if
you see a different device name, you can add the lines
printer:
device: /dev/rfcomm1
(or whatever) to the m120-print
command’s configuration file in
$HOME/.config/m120-print.yml
.
At this point, you should be able to generate and print a label from an input file like the one above, using
$ m120-print label.txt
If you’d rather not waste actual sticky labels while you’re tweaking the label layout, you can generate PNG output using
$ m120-print --format=png --output=label.png label.txt
or “ASCII art” (preferably in a huge window with a tiny font) using
$ m120-print --format=text label.txt
Of course, having to generate label description files by hand generally sucks. What we would really like to do is print publication labels directly from the SCDDB web site, preferably from the “Publications” tab. The picture shows how this might work – we check the publications for which we want to print a label, and then click on the “Print Labels” button to start the actual printout.
The obvious problem here is, how do we get the browser to generate the
label description file and run the m120-print
command on our behalf?
In general, browsers can’t start programs on the host they’re running
on because that would be a vast security hole, a disaster waiting to
happen. But it turns out that under certain circumstances it is
possible to enlist locally-running software to do tasks that a browser
can’t perform itself. Obviously we can’t allow a browser to run
arbitrary Linux commands, but what is allowed is for the browser to
use a mechanism called native messaging to run very specific
preconfigured commands that have presumably be suitably vetted to
disallow any shenanigans. The process uses two steps (or two and a
half, depending on how you want to see it).
A special extension for the Chrome browser adds the “Print Labels”
button to the dance collection details tab. (This is not part of
the general UI that every SCDDB user gets to see, because it only
makes sense for people who actually have a label printer.) This
includes a short JavaScript snippet which describes what happens
when you click on the button, to wit, we fetch the
CollectionPublicationItem
IDs of all the checked items on the
page and pass them on, together with the database ID of the
collection itself, to the “service worker” of the Chrome extension.
(This is necessary because the “content scripts” of Chrome
extensions don’t have direct access to native messaging. Only
service worker scripts are allowed to do native messaging, so our
web page button needs to ask the service worker for help.)
The service worker sends a request, via Chrome’s native messaging
mechanism, to the “native messaging host”. This is a specially
preconfigured Python script that runs on the computer alongside the
browser, and is exempt from the browser’s security restrictions –
which means it can do things like prepare a label description and
call the m120-print
command to have it sent to the label
printer.
The m120-print
command does the actual heavy lifting as discussed
in the previous section.
It should be mentioned at this point that from a security POV there is
very little to worry about as far as our label printing mechanism is
concerned. Normally what causes problems is when users get to control
the input (because they can insert nasty stuff that the software is
unprepared to handle), but here the only information that passes from
the browser to the native host are a collection ID and one or more
publication item IDs. The native messaging host uses these to retrieve
the actual data from the database, and it does so via the SCDDB’s
standard HTTP API with the configured SCDDB credentials of the
computer’s owner, so the native messaging host can’t do anything that
the computer’s owner couldn’t otherwise do. Conversely, all the
interesting information that the native messaging host and the
m120-print
command are dealing with comes directly from the
database and is presumably secure.
Here’s how to make this all work. The first step is to install the
native messaging host. This is a Python program called
scddb-host.py
, and it must be made known to the Chrome browser by
putting a JSON “manifest” like
{
"name": "org.strathspey.scddb",
"description": "Support for SCDDB (including label printing)",
"path": "/opt/scddb/chrome/scddb-host.py",
"type": "stdio",
"allowed_origins": ["chrome-extension://ikdhmjkacgkacbmilcfdmaggiljgphhb/"]
}
into the file
$HOME/.config/google-chrome/NativeMessagingHosts/org.strathspey.scddb.json
(you must make sure that this name matches exactly because otherwise
Chrome won’t be able to find the file). Note that if you’re using
Chromium or Firefox, this should also work but the file name for the
manifest will be different.
Ensure that the path
corresponds to where the native messaging host
is installed on your system, and that the scddb-host.py
file is
marked executable. We may need to patch up the extension’s URL in
allowed_origins
later to mae sure that it agrees with the
extension’s actual ID.
You will also need to create a configuration file for the native
messaging host in $HOME/.config/scddb/scddb-host.yml
. At a minimum,
this should contain your SCDDB access credentials, which are required
for the native messaging host to be able to obtain collection and
publication information for the labels to be printed:
username: hugo
password: SeCrEtPaSsWoRd123
(See the end of this document for more stuff you can put in the configuration file, but shouldn’t have to.)
Next, you need to install the Chrome extension. Select “Extensions”
from the three-dots menu in Chrome and pick “Manage Extensions” from
the submenu. This shows you a page with all the extensions you have
already installed. (You can also navigate to chrome://extensions
directly.) Use the slider switch in the top right corner to enable
“Developer mode”, which is what allows you to install extensions
directly from your own computer. Then use the “Load unpacked” button
near the top left corner of the page to bring up a file selection
dialog, where you must select the folder containing the Chrome
extension (chrome/ext
in the ace4-labels
folder). This should make
the Chrome-SCDDB extension appear among the other extensions on the
page.
If you now navigate to the “Publications” tab of one of your dance collections, you should see the “Print Labels” button appear in the button bar above the publications table. You can try to select a publication in the table and then click on “Print Labels” to see if a label is printed.
What to do if things don’t seem to work? Here are a few hints:
Make sure the m120-print
command can process a label description
and write a PNG file or text representation of the label. Put the
sample label description into a file (e.g., label.txt
), and run
m120-print --output=png label.txt
. This should generate a file
called label.png
which you can inspect using an image viewer to
check that it looks reasonable. Things that can go wrong at this
stage mainly include missing Python libraries (the m120-print
command needs the freetype
and qrcode
extensions, which you can
install either using pip
or, on a Debian GNU/Linux system, by
using apt
to obtain the python3-freetype
and python3-qrcode
extensions and their dependencies). On Debian GNU/Linux you will
also want to install the B612 fonts from the fonts-b612
package;
the B612 fonts are otherwise freely available from
https://b612-font.com.
Make sure the label printer is switched on and connected to your
computer via Bluetooth as discussed earlier. In particular, satisfy
yourself that /dev/rfcomm0
(or whatever) is the correct device
name and that you have write permission for it (on Debian
GNU/Linux, you should be in the dialout
group). Try printing
a label using m120-print label.txt
.
Make sure the native messaging host can call the m120-print
command to print a label. You can use a command like
$ echo -en '\072\000\000\000{"op":"publication-label", "collection":1, "items":[1001]}' | /opt/scddb/chrome/scddb-host.py | xxd
to call the native messaging host like Chrome would (but be aware
that the collection ID and publication item ID won’t work for you;
you can get your collection ID from the detail page – it’s the grey
number to the right of the collection name – and an item ID by
using the browser’s development tools to inspect a table row; the
item ID for that table row is the value of the id
attribute of
its tr
element, less the p
at the beginning; you will also need
to adjust the first character of the string to reflect the length
of the JSON data – from {
to }
– in octets, as an octal number).
Any error messages should show up in the hexadecimal dump of the
native messaging host’s standard output.
You should feel free to adjust the configuration of the native
messaging host in the $HOME/.config/scddb-host.yml
file such that
the printcmd
being executed is something like m120-print --format=png --output=/tmp/label.png
, to avoid excessive waste of
sticky labels. Remember to change it back when you’re ready to
print actual labels again.
The next step is to verify that the service worker calls the
native messaging host correctly. Bring up the Chrome-SCDDB
extension’s icon in the browser’s navigation bar, open the
dropdown menu (it’s not much of a menu right now), right-click on
it and select Inspect
(the bottom entry). This should bring
up a “DevTools” window at the bottom of which is a JavaScript
console. Try printing a label and watch for messages appearing
there; ideally you should see a message saying
messaging host said: > {status: 'OK'}
but if anything has gone wrong an error message from the native messaging host should appear, which you can analyse in order to rectify the underlying cause.
One possible problem with talking to the native messaging host is
that the ID of the Chrome-SCDDB extension in your browser may not
match the one in the allowed_origins
clause in your native host
messaging manifest. You can find the actual extension ID in the
extension’s card on the chrome://extensions
page, so make sure
this and the one in the org.strathspey.scddb.json
file are the
same.
Both the native messaging host and the m120-print
command use YAML
files to store their configuration settings.
The configuration file for the native messaging host is in
$HOME/.config/scddb/scddb-host.yml
. It can contain a dictionary with
the items listed below, most of which have sensible hard-coded defaults:
url
https
.)printcmd
~/bin/m120-print
(which is the default). If the value
of this item is a string, it will be split according to shell rules
using the Python shlex.split()
function, but you can also specify
a list in YAML to avoid this. In either case, the first item of the
resulting list will be processed to expand a leading ~
in the
usual manner.debug
true
or false
). If true
, a copy of the label
description file will be appended to /tmp/label.txt
. (Normally,
the native messaging host pipes the label description(s) to the
printcmd
’s standard input in order to avoid having to write files,
but the /tmp/label.txt
file can be useful for debugging.) Defaults
to false
.username
and password
label_template
label 320 240
font B612-Regular 32
text 10 38 "{publication_publisher_displayname}"
text 10 80 "{publication_name}"
font . 45
textr 316 118 {publication_id}
font B612-Mono 64
text 10 170 {extra1}
text 10 228 {extra2}
qrcode 211 129 https://my.strathspey.org/dd/publication/{publication_id}/
print
and you should feel free to tweak this if you must. This will be
used in a Python str.format_map()
call to insert the results of
the collection publication item retrieval operation.The configuration file for the m120-print
command is in
$HOME/.config/m120-print.yml
and can contain the following items:
printer
This is a dictionary containing the following items:
device
/dev/rfcomm0
in most circumstances
(which is the hard-coded default).header
, marker
and footer
fonts
This is a dictionary that maps font names as used in label descriptions to font file names on your computer. If you want to use fonts other than the two B612 fonts provided by the system, feel free to add additional lines to this dictionary.
Sign in to see recent visitors!