We're using cookies to make this site more secure, featureful and efficient.

Printing Publication Labels for Collections

The code for this project is available from the ace4-labels project.

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.

The Phomemo M120 label printer.
The Phomemo M120 label printer.
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.

Preparing and Printing Labels

A sample label.
A sample label.
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 is 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
Selects the font 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
Renders the literal text 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
Like 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
Renders a QR code encoding the literal text T, 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

Printing labels from the SCDDB.
Printing 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).

  1. 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.)

  2. 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.

  3. 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.

Troubleshooting

What to do if things don’t seem to work? Here are a few hints:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

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 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
A Boolean value (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
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}/
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
The name of the Linux device corresponding to the label printer. This should be /dev/rfcomm0 in most circumstances (which is the hard-coded default).
header, marker and footer
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.

Recently seen

Sign in to see recent visitors!