Get printer status using device under /dev

I am attempting to get the printer status by writing to a printer character device under /dev , but it will not let me write to the /dev device. I have a C program that works outside of Balena, but not in my Balena container.

I have a Balena container that has an attached thermal printer… that works to print PDF files using CUPS and lp command.

However, to detect if the printer is out of paper, I cannot use CUPS, but I have to drop to a lower level… and have to read/write using the device under /dev.
But that gives me an error code “22” of “Invalid argument” when I try to write to the device using a C program.

My C program works on my non-containerized pi. But in a Balena container I get the error code 22.
This feels like a permissions problem, but my Balena container application is a single container, that means it is “privileged”, correct?

I’m pretty sure I’m using the correct character device /dev/bus/usb/001/004 , as shown by the commands below.

I am able to open the device, but when I try to write to it I get the error.

Is there some other device or permission I’m missing?

Thank you for your help.

# lsusb
...
Bus 001 Device 004: ID 0dd4:0205 Custom Engineering SPA
...

# ls -l /dev/bus/usb/001/004
crw-rw-r-- 1 root lp 189, 3 Mar 11 12:07 /dev/bus/usb/001/004

# usb-devices
...
T:  Bus=01 Lev=01 Prnt=01 Port=02 Cnt=03 Dev#=  4 Spd=12  MxCh= 0
D:  Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
P:  Vendor=0dd4 ProdID=0205 Rev=03.34
S:  Manufacturer=CUSTOM Engineering S.p.A.
S:  Product=VKP80III
S:  SerialNumber=VKP80III PRN Num.: 0
C:  #Ifs= 2 Cfg#= 1 Atr=c0 MxPwr=2mA
I:  If#= 0 Alt= 0 #EPs= 2 Cls=07(print) Sub=01 Prot=02 Driver=(none)
I:  If#= 1 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=usb-storage
...

This is an excerpt from my C program:

fd = open("/dev/bus/usb/001/004", O_RDWR | O_EXCL);
if (fd < 0) {
    printf("Failed opening device\n");
    return -1;
}
result = write(fd, (const void *)"\x10\x04\x14", 3);
if (result < 0) {
    printf("Failed writing printer status command: line %d\n", __LINE__);
    printf("errno: %d\n", errno);
    perror("Error");
    close(fd);
    return -1;
}

The write() fails with this output from errno and perror()

Failed writing printer status command: line 91
Errno: 22
Error: Invalid argument

Hey @pzarfos

First things first, are you using a balena base image? We do mount some dev filesystem things into the container, so this could be something to try.

If not, you could also try to use the docker-compose field devices, more on that here: https://docs.docker.com/compose/compose-file/compose-file-v2/#devices

Let us know how this goes!

I am using this balena base image:
FROM resin/%%BALENA_MACHINE_NAME%%-ubuntu-node

I tried writing to /dev/bus/usb/001/004 but that gave me the write error.
The container does not have a /dev/usb/lp0 like I was expecting.
Does balena mount any other dev filesystem things that I should look for to find the printer device?

I am using a single container application. Can I use a docker-compose field “devices” in a single container Dockerfile.template? I thought the devices directive was for multi-container?

Hi @pzarfos , can you try switching to the newer balenalib base images the resin/ ones are deprecated and not getting updated anymore. Our balenalib ones do indeed mount /dev file system, you can see the ubuntu version here: https://github.com/balena-io-library/base-images/blob/master/balena-base-images/armv7hf/ubuntu/bionic/run/entry.sh .

Note you should also add ENV UDEV=on in your dockerfile, which will make udevd start up in your container and ensure any later usb plugged devices will be applied/created in /dev.

@pzarfos, did you get a chance to try your app with a balenalib base image? Just checking how you’re getting on, or if this issue was resolved. Thanks!

I have been working on I switching FROM resin to FROM balenalib, but that opened a whole new can of worms.

I started with a single-container Dockerfile.template that used INITSYSTEM and systemctl to run some of my own daemons written in python …but the balenalib gave a warning that systemctl would not be supported.

Question 1: so to run my own daemons, does that mean you recommend a multi-container environment where I run each daemon in its own container? (instead of using systemctl all in the same container?)

Question 2: the balenalib base images had the same problem writing to the printer device as the resin images. Neither had the /dev/usb/lp0 like I was expecting from a bare metal ubuntu installation. Is writing to /dev/bus/usb/001/004 the correct approach? or should there be a generic /dev/usb/lp0 available when I plug in a USB thermal printer.

Question 3: if the answer to #2 is “yes”, then my USB device might change device numbers when they are plugged/unplugged, how do I specify that in my Dockerfile device mapping directive?

Sorry for the barrage of questions.

@pzarfos, thanks for these questions – we are happy to help! :slight_smile:

In case you haven’t (and forgive me if you already have), please be sure to have a look at the following sections of balenalib’s documentation (they’re actually all consecutive sections on the same page):

Regarding your question 1, both alternatives are equally valid and justifiable. If you were starting from scratch, I’d say that this quote from the last link above applies: “we now recommend the use of multiple containers and no longer recommend the use of an initsystem, particularly systemd, in the container as it tends to cause a myriad of issues, undefined behaviour and requires the container to run fully privileged.”

But these “myriad of issues” equally applied the older ‘resin’ images, and what used to work under the older ‘resin’ images will work just as well with balenalib images by following the configuration examples in the same section of the documentation (Installing your own Initsystem). So if you had something working with the resin images (perhaps apart from the printer issue), it probably makes sense to stick with the daemons running in a single container, with an initsystem.

Re “my Balena container application is a single container, that means it is “privileged”, correct?”, that’s correct, check also this page: https://www.balena.io/docs/learn/develop/hardware/

Re question 2:

  • Please confirm you have ENV UDEV=1 in your Dockerfile.
  • Just as an additional data point, does /dev/usb/lp0 exist when listed on a host OS terminal?
  • My gut feeling is that there should be a generic /dev/usb/lp0 path, or alternative such path. I have limited knowledge of this, but I would guess that the data read/written to /dev/bus/usb/001/004 is at the binary USB protocol level. Not the printer protocol level, which is probably what you need, but a level below that. I assume that the binary printer protocol would sit on top of a binary USB protocol, and I guess you need the former not the latter.
  • This link doesn’t answer the question, but seems to describe something very similar to what you are observing: https://raspberrypi.stackexchange.com/questions/9591/cant-write-to-dev-bus-usb-001-005
    • Actually, what’s your device’s hardware? Is it a Raspberry Pi?
  • I note that you wrote that “I have a Balena container that has an attached thermal printer… that works to print PDF files using CUPS and lp command.” To confirm, do you mean that the same container that fails to list /dev/usb/lp0, and that denies permission to write to the USB device, can successfully print using CUPS and lp?
    • If so, surely this means that the container has write permissions “to the printer” – and then I don’t think this issue is about permissions.
  • Re “neither had the /dev/usb/lp0 like I was expecting from a bare metal ubuntu installation,” do you mean that when running Ubuntu on your device, rather than balenaOS, /dev/usb/lp0 shows up? I mean on the same hardware.
  • Unlikely to be relevant, but the open call in your C program uses the O_EXCL flag. What’s the point of O_EXCL?

Re question 3, I think it doesn’t apply because I think the answer to Q2 is no. But if I’m wrong then we should revisit this question. :slight_smile:

So, check if anything in this reply helps to get you forward, and feel free to come back with further questions!

Actually, in my previous reply I included a link that I said does not answer the question, but maybe it does! If I understood it right, that CUPS + lp are working in your container, then that link shows how to send raw bytes to the printer through lp, meaning you might not even need your C program:

The hardware I am using is an Intel NUC and a Custom Engineering VKP80III thermal printer.

When running Ubuntu on the bare hardware, it does have a /dev/usb/lp0 and my C program runs fine. (Also runs on a bare metal pi, but I’m not using that for my application)

When I run BalenaOS on the Intel NUC, I have the write error to /dev/bus/usb/001/004 and also the /dev/usb/lp0 is not created.

Is there a way to tell the BalenaOS container to create the /dev/usb/lp0 device?
I think that would solve my problem.

Also, your lp -d printer_name -o raw - <<< $'\x10\x04\x14' trick worked as far as no write error… but it kicked the printhead instead of being silent, which is not ideal. Is there a way to read binary bytes off the printer from the lp command?

@pzarfos, that’s usually what a udev rule would achieve, by automatically creating a soft link. If you run ls -l /dev/usb/lp0 on “Ubuntu on the bare hardware”, is it a soft link? Perhaps something like:

$ ls -l /dev/usb/lp0
/dev/usb/lp0 -> /dev/bus/usb/001/004

You can also create the soft link manually for testing:

$ sudo ln -s /dev/bus/usb/001/004 /dev/usb/lp0

I cannot say that I am optimistic about this soft link solving the problem… But if it does, you can make it persistent by adding a udev rule to the config.json file of a balenaOS image. Some pointers on udev rules:

Is there a way to read binary bytes off the printer from the lp command?

Hmm I don’t think so, however if you are comfortable with C code (you wrote a C app…), you can have a look at lp’s source code, and perhaps find out how they are reading/writing from/to the printer:

I tried writing to that device many different ways, and they all came back with some sort of write error or device busy or operation timed out.

LOL, well I was trying to avoid diving into the CUPS code… but getting into the code was a good idea because there is a CUPS API… I think I’ll investigate that approach next.

Thanks, please let us know how that went for you.

Thanks, please let us know how that went for you.

Try to introduce FROM resin/%%BALENA_MACHINE_NAME%%-ubuntu-node, it should help