| chrome | ||
| imgs | ||
| m120-print.py | ||
| README.md | ||
| scddb-host.py | ||
Printing Publication Labels for Collections
Background
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 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.
Preparing and Printing Labels
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 title of the publication (or as much as will fit on one line)
- The publisher/author of the publication (or as much as will fit on one line) – many SCD books are self-published while others are published by SCD clubs and RSCDS branches.
- The numerical ID of the publication in the SCD database.
- The shelf code for the publication in the collection.
- A QR code for the detail page of the publication in the database.
(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: Starts a new label which isWpixels wide andHpixels high (whereWandHare literal integers). For nowWmust be 320 andHmust be 240, but this is not enforced.font F S: Selects the fontFat sizeS.Fmust be the name of a font in thefontssection of the configuration file (which basically consists of a dictionary mapping font names to font file names).Smust be an integer. By default, we use theB612font, which is a highly legible font designed by the Airbus corporation for use on aircraft instrument panels. For convenience, ifFis.(a period), the font name remains unchanged and only the size changes toS.text X Y T: Renders the literal textT, in the currently selected font, starting at position (X,Y). (Note thatYrefers 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: Liketext, except that it renders the text flush-right, i.e., theXvalue describes where the right end of the text baseline sits.qrcode X Y T: Renders a QR code encoding the literal textT, with its top left corner sitting at (X,Y).print: Sends the current label to the printer.
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
Generating Labels from the SCDDB
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
CollectionPublicationItemIDs 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-printcommand to have it sent to the label printer. -
The
m120-printcommand 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.
Troubleshooting
What to do if things don't seem to work? Here are a few hints:
-
Make sure the
m120-printcommand 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 runm120-print --format=png label.txt. This should generate a file calledlabel.pngwhich 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 (them120-printcommand needs thefreetypeandqrcodeextensions, which you can install either usingpipor, on a Debian GNU/Linux system, by usingaptto obtain thepython3-freetypeandpython3-qrcodeextensions and their dependencies). On Debian GNU/Linux you will also want to install the B612 fonts from thefonts-b612package; 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 thedialoutgroup). Try printing a label usingm120-print label.txt. -
Make sure the native messaging host can call the
m120-printcommand 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 | xxdto 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
idattribute of itstrelement, less thepat 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.ymlfile such that theprintcmdbeing executed is something likem120-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 sayingmessaging 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_originsclause in your native host messaging manifest. You can find the actual extension ID in the extension's card on thechrome://extensionspage, so make sure this and the one in theorg.strathspey.scddb.jsonfile are the same.
Configuration Files
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- The URL of the Strathspey SCDDB server used to access the
API. Unless you're doing development on the server itself, this will
be https://my.strathspey.org/dd/api/, which is indeed the
hard-coded default. (If you do change this, be sure to quote the URL
in the YAML file because otherwise the YAML processor will take
exception to the colon after
https.) printcmd- The command used to print the label. This will most likely be a
variation on
~/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 Pythonshlex.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- A Boolean value (
trueorfalse). Iftrue, 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 theprintcmd's standard input in order to avoid having to write files, but the/tmp/label.txtfile can be useful for debugging.) Defaults tofalse. usernameandpassword- Your credentials for the SCDDB. This is needed to get collection and publication information from the database for use in labels. No defaults – you need to set this yourself.
label_template- A template used to generate label description files. By default this
looks like
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}/ printand 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- The name of the Linux device corresponding to the label
printer. This should be
/dev/rfcomm0in most circumstances (which is the hard-coded default).header,markerandfooter - These are sequences of control characters which we must send to the printer to make label printing work. They're written out as bytes in hexadecimal notation (two characters per byte); spaces will be ignored. Don't change these unless you know what you're doing and/or have lots of spare sticky labels.
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.


