Support for LabelManager 280
pschonmann opened this issue ยท 42 comments
Hi i have Linux mint and when run program i got this error message
The device 'Dymo LabelManager PnP' could not be found on this system.
When i print test page in printer dialog, printer works.
Im using Label Manager 280
Hi @pschonmann, that's because the Label Manager 280 is different from the LabelManager PnP.
It would be nice to support other models like this one, but it would probably take some work. Would you be interested in figuring out and implementing an interface for this model?
Hi, thanks for fast reply.
Im interested, but have no python programming skills.
Are you able to make sense of this? I think that figuring out the device IDs is probably the place to start.
Although this was almost 10 years ago, and I suspect there's an easier way now. I'd actually like to rewrite this whole project.
Hi!
I just got the LabelManager 280 as well, and noticed it's not working with dymoprint :(
In dmesg it looks like this:
usb 1-1.1: new full-speed USB device number 10 using ehci-pci
usb 1-1.1: New USB device found, idVendor=0922, idProduct=1005, bcdDevice= 1.00
usb 1-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-1.1: Product: DYMO LabelManager 280
usb 1-1.1: Manufacturer: DYMO
usb 1-1.1: SerialNumber: 14424821032015
And in lsusb i have:
Bus 001 Device 010: ID 0922:1005 Dymo-CoStar Corp. DYMO LabelManager 280
In verbose it's:
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 9 Hub
bDeviceSubClass 0
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 64
idVendor 0x1d6b Linux Foundation
idProduct 0x0002 2.0 root hub
bcdDevice 5.15
iManufacturer 3 Linux 5.15.13-gentoo-x86_64 ehci_hcd
iProduct 2 EHCI Host Controller
iSerial 1 0000:00:1a.0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0019
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xe0
Self Powered
Remote Wakeup
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0004 1x 4 bytes
bInterval 12
If you have any ideas how to make it work I'd be glad to test them, unfortuantely my skills aren't up to the task of reverse-engineering it from scratch....
Ah, and i tried changing the ProoductIDs in udev and usb_modeswitch, no dice :(
Hi @Scorcerer, thanks for the detailed logs, that's very helpful. For how to proceed, please see my comments here: #30 (comment) In particular, you should focus on getting usb_modeswitch to work. (The usb_modeswitch tool sends a magical 3-byte packet 1B 5A 01
which actually enables the printer device as opposed to the storage device with the Windows drivers.)
So, i started with:
Bus 001 Device 013: ID 0922:1005 Dymo-CoStar Corp. DYMO LabelManager 280
Then i ran:
$ usb_modeswitch --default-vendor 0922 --default-product 1005 --message-content 1B5A01
Look for default devices ...
Found devices in default mode (1)
Access device 013 on bus 001
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 7
Use endpoints 0x02 (out) and 0x81 (in)
Looking for active drivers ...
Set up interface 0
Use endpoint 0x02 for message sending ...
Trying to send message 1 to endpoint 0x02 ...
OK, message successfully sent
Reset response endpoint 0x81
Reset message endpoint 0x02
-> Run lsusb to note any changes. Bye!
Afterwards it's still:
Bus 001 Device 013: ID 0922:1005 Dymo-CoStar Corp. DYMO LabelManager 280
I also tried:
$ usb_modeswitch --default-vendor 0922 --default-product 1005 --message-endpoint 01 --message-content 1B5A01
Look for default devices ...
Found devices in default mode (1)
Access device 013 on bus 001
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 7
Use endpoints 0x01 (out) and 0x81 (in)
Looking for active drivers ...
Set up interface 0
Use endpoint 0x01 for message sending ...
Trying to send message 1 to endpoint 0x01 ...
Sending the message returned error -1. Try to continue
Reset response endpoint 0x81
Reset message endpoint 0x01
Could not reset endpoint (probably harmless): -5
-> Run lsusb to note any changes. Bye!
But same thing, no change. Can it be that a different magic string is needed to make it work?
@Scorcerer, excellent work! I tried your commands on my LabelManager PnP with the following results:
$ lsusb | grep 0922
Bus 001 Device 014: ID 0922:1001 Dymo-CoStar Corp. LabelManager PnP
$ usb_modeswitch --default-vendor 0922 --default-product 1001 --message-content 1B5A01
Look for default devices ...
Found devices in default mode (1)
Access device 014 on bus 001
Error opening the device. Abort
$ sudo usb_modeswitch --default-vendor 0922 --default-product 1001 --message-content 1B5A01
Look for default devices ...
Found devices in default mode (1)
Access device 014 on bus 001
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 3
Error: message endpoint not given or found. Abort
$ sudo usb_modeswitch --default-vendor 0922 --default-product 1001 --message-endpoint 01 --message-content 1B5A01
Look for default devices ...
Found devices in default mode (1)
Access device 014 on bus 001
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 3
Error: response endpoint not given or found. Abort
$ sudo usb_modeswitch --default-vendor 0922 --default-product 1001 --message-endpoint 01 --response-endpoint 01 --message-content 1B5A01
Look for default devices ...
Found devices in default mode (1)
Access device 014 on bus 001
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 3
Use endpoints 0x01 (out) and 0x01 (in)
Looking for active drivers ...
OK, driver detached
Set up interface 0
Use endpoint 0x01 for message sending ...
Trying to send message 1 to endpoint 0x01 ...
OK, message successfully sent
Reset response endpoint 0x01
Could not reset endpoint (probably harmless): -99
Reset message endpoint 0x01
Could not reset endpoint (probably harmless): -99
-> Run lsusb to note any changes. Bye!
$ lsusb | grep 0922
Bus 001 Device 015: ID 0922:1002 Dymo-CoStar Corp.
There are a few minor differences which may be worth looking into (like setting the response endpoint). It could certainly be that the magic string is different. I'm honestly no expert on this stuff, and have no idea.
If you have access to a Windows machine, maybe you could try to see if you could sniff the message. But then again, if the mode switching string is different, probably all the other control strings are different, so it's probably not worthwhile.
I just now saw that i posted in wrong lsusb -v codeblock. This is the right one:
Bus 001 Device 015: ID 0922:1005 Dymo-CoStar Corp. DYMO LabelManager 280
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 16
idVendor 0x0922 Dymo-CoStar Corp.
idProduct 0x1005
bcdDevice 1.00
iManufacturer 1 DYMO
iProduct 2 DYMO LabelManager 280
iSerial 3 14424821032015
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0020
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xc0
Self Powered
MaxPower 50mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 7 Printer
bInterfaceSubClass 1 Printer
bInterfaceProtocol 2 Bidirectional
iInterface 4 Printer-Class
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Device Status: 0x0001
Self Powered
I'll try to install it via CUPS with the official driver and let you know about the results (seeing it's reported as printer, maybe it'll "just work"
Can you post yours for comparison, before and after modeswitch?
$ lsusb | grep 0922
Bus 001 Device 016: ID 0922:1001 Dymo-CoStar Corp. LabelManager PnP
$ sudo lsusb -v -s 001:016
Bus 001 Device 016: ID 0922:1001 Dymo-CoStar Corp. LabelManager PnP
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x0922 Dymo-CoStar Corp.
idProduct 0x1001 LabelManager PnP
bcdDevice 1.00
iManufacturer 1 Dymo
iProduct 2 DYMO LabelManager PnP
iSerial 3 02281002072010
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0040
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 34
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 10
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 10
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 8 Mass Storage
bInterfaceSubClass 6 SCSI
bInterfaceProtocol 80 Bulk-Only
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
can't get device qualifier: Resource temporarily unavailable
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0000
(Bus Powered)
$ sudo usb_modeswitch --default-vendor 0922 --default-product 1001 --message-endpoint 01 --response-endpoint 01 --message-content 1B5A01
Look for default devices ...
Found devices in default mode (1)
Access device 016 on bus 001
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 3
Use endpoints 0x01 (out) and 0x01 (in)
Looking for active drivers ...
OK, driver detached
Set up interface 0
Use endpoint 0x01 for message sending ...
Trying to send message 1 to endpoint 0x01 ...
OK, message successfully sent
Reset response endpoint 0x01
Could not reset endpoint (probably harmless): -99
Reset message endpoint 0x01
Could not reset endpoint (probably harmless): -99
-> Run lsusb to note any changes. Bye!
$ lsusb | grep 0922
Bus 001 Device 017: ID 0922:1002 Dymo-CoStar Corp.
$ sudo lsusb -v -s 001:017
Bus 001 Device 017: ID 0922:1002 Dymo-CoStar Corp.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x0922 Dymo-CoStar Corp.
idProduct 0x1002
bcdDevice 1.00
iManufacturer 1 Dymo
iProduct 2 DYMO LabelManager PnP
iSerial 3 02281002072010
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0057
bNumInterfaces 3
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 7 Printer
bInterfaceSubClass 1 Printer
bInterfaceProtocol 2 Bidirectional
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x85 EP 5 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x05 EP 5 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 8 Mass Storage
bInterfaceSubClass 6 SCSI
bInterfaceProtocol 80 Bulk-Only
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 34
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 10
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 10
can't get device qualifier: Resource temporarily unavailable
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0000
(Bus Powered)
@Scorcerer, I just took a look at your lsusb
codeblock, and it does indeed appear that the device is already exposing a printer without using usb_modeswitch.
So side-by side it looks like there are minor differences in endpoint addresses and stuff, but both devices announce themselves as printers (mine from the beginning, hours after usb_modeswitch)
Would it be possible for dymoprint to accept different addressing or stuff? Idk how it talks to the printer, but from what i see here, this should be working....
@Scorcerer, how are your Python skills? Can you manage an editable install? (i.e. git clone, and then pip install -e .
)
Changed like this:
https://github.com/Scorcerer/dymoprint/blob/LM280_testing/src/dymoprint/constants.py
but still:```
(.venv) scor@Hojo ~/Projects/dymoprint $ pip install -e .
Obtaining file:///home/scor/Projects/dymoprint
Installing build dependencies ... done
Checking if build backend supports build_editable ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Requirement already satisfied: appdirs in ./.venv/lib/python3.9/site-packages (from dymoprint==1.0.1.post1.dev59+gfb42b08) (1.4.4)
Requirement already satisfied: python-barcode>=0.13.1<1 in ./.venv/lib/python3.9/site-packages (from dymoprint==1.0.1.post1.dev59+gfb42b08) (0.13.1)
Requirement already satisfied: Pillow<9,>=8.1.2 in ./.venv/lib/python3.9/site-packages (from dymoprint==1.0.1.post1.dev59+gfb42b08) (8.4.0)
Requirement already satisfied: PyQRCode<2,>=1.2.1 in ./.venv/lib/python3.9/site-packages (from dymoprint==1.0.1.post1.dev59+gfb42b08) (1.2.1)
Installing collected packages: dymoprint
Attempting uninstall: dymoprint
Found existing installation: dymoprint 1.0.1.post1.dev58+g3e26dd3.d20220125
Uninstalling dymoprint-1.0.1.post1.dev58+g3e26dd3.d20220125:
Successfully uninstalled dymoprint-1.0.1.post1.dev58+g3e26dd3.d20220125
Running setup.py develop for dymoprint
Successfully installed dymoprint-1.0.1.post1.dev59+gfb42b08
(.venv) scor@Hojo ~/Projects/dymoprint $ whereis dymoprint
dymoprint: /home/scor/Projects/dymoprint/.venv/bin/dymoprint
(.venv) scor@Hojo ~/Projects/dymoprint $ dymoprint Machine_test
The device 'Dymo LabelManager 280' could not be found on this system.
Looks fundamentally good. With the editable install, you should be able to modify the source code without the need to reinstall.
This is the part of code which we need to diagnose.
You should be able to run python
, or more conveniently ipython
, and then run
from dymoprint.constants import (
DEV_CLASS,
DEV_NAME,
DEV_NODE,
DEV_PRODUCT,
DEV_VENDOR,
FONT_SIZERATIO,
USE_QR,
QRCode,
e_qrcode,
)
from dymoprint.utils import getDeviceFile
dev = getDeviceFile(DEV_CLASS, DEV_VENDOR, DEV_PRODUCT)
dev
For me, the value of dev
is the string /dev/hidraw8
.
Haha, actually it's much simpler...
First try
$ ls -al /sys/bus/hid/devices/*0922*
lrwxrwxrwx 1 root root 0 Jan 25 22:47 /sys/bus/hid/devices/0003:0922:1002.001A -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-5/1-5.3/1-5.3:1.2/0003:0922:1002.001A
That's essentially what getDeviceFile
does.
Hmm, no dice:
(.venv) scor@Hojo ~/Projects/dymoprint $ ls -al /sys/bus/hid/devices/*0922*
ls: cannot access '/sys/bus/hid/devices/*0092*': No such file or directory
Although there are some things in there, looks like printer is not HID-recognized :(
There's one last thing you could try. If you go back to the original dymoprint they use Python's USB library instead of the device file.
Hah! reading through https://sbronner.com/dymoprint.html I opted for installing it via CUPS. It freaking works, as in "it pushed out tape when i clicked 'print'"
Now the question is how to print proper labels and stuff out of it, preferably simply from commandline...
Wow, it's been a long time since I've read that text, but after our experience, I finally understand what he is saying! ๐
That's awesome that it responds to CUPS!!! ๐ No idea about proper labels, i suppose we should learn some CUPS.
Also, based on our work above, i think we could put together a very useful document about probing DYMO printers so that others can work on supporting their own devices. What do you think?
Yup, sounds like a plan :) That means i'm gonna have to figure out how to pritn with LM280 quickly, as i was able to get Rhino 6000 cheaply (it seems it also works with D1 tapes) and it's gonna be the next project :)
I'll try to figure out how to print with CUPS, as for example for me it'd make sense to use commandline in a quick'n'dirty way to print a label, without playing extensively in some kind of label editing software.
Ping @varac, since they also were interested in the 280. (See #30 (comment))
Also, I don't know if it's relevant to CUPS, but this technical manual is a good guide to the commands:
https://download.dymo.com/dymo/technical-data-sheets/LW%20450%20Series%20Technical%20Reference.pdf
BTW, the modeswitch command is <esc> Z <soh>, which sort of fits in with the protocol.
Hi, trying to use LabelManager 280 via CUPS. I've managed to compile and install the CUPS drivers (Ubuntu detects the LM280 printer when it is connected and powered on)
Got the updated driver source from THIS REPO
I've tried installing dymoprint from YOUR REPO but
running dymoprint hello
results in The device 'Dymo LabelManager 280' could not be found on this system.
Any suggestions I've might be doing wrong?
regarding print command oneliner
Also i've set it as default printer
lpstat -p -d
lpoptions -d LabelManager-280
tryied to print text using command as shown HERE with no success. Ubuntu shows that printing in progress, the finished but LM280 did nothing.
@aleksasp, I think the problem is that the printer is not showing up as a HID device as the current repo expects. If you run
ls -al /sys/bus/hid/devices/*0922*
then I think you will get "No such file or directory". Could you confirm this?
In this case, you may instead have success with the original dymoprint script by Sebastian Bronner. That uses USB directly, instead of the HID virtual file. So I think you just need to add 0x1005
to _USB_PRODUCT
and make sure all the required packages are installed.
@maresb you were right, just had also to change the device class to 0x7
instead of 0x3
and change path to an existing ttf font.
Below is the output of
print(dev.get_active_configuration())
that suggested changing device class to 0x7
.
CONFIGURATION 1: 50 mA ===================================
bLength : 0x9 (9 bytes)
bDescriptorType : 0x2 Configuration
wTotalLength : 0x20 (32 bytes)
bNumInterfaces : 0x1
bConfigurationValue : 0x1
iConfiguration : 0x0
bmAttributes : 0xc0 Self Powered
bMaxPower : 0x19 (50 mA)
INTERFACE 0: Printer ===================================
bLength : 0x9 (9 bytes)
bDescriptorType : 0x4 Interface
bInterfaceNumber : 0x0
bAlternateSetting : 0x0
bNumEndpoints : 0x2
bInterfaceClass : 0x7 Printer
bInterfaceSubClass : 0x1
bInterfaceProtocol : 0x2
iInterface : 0x4 Printer-Class
ENDPOINT 0x81: Bulk IN ===============================
bLength : 0x7 (7 bytes)
bDescriptorType : 0x5 Endpoint
bEndpointAddress : 0x81 IN
bmAttributes : 0x2 Bulk
wMaxPacketSize : 0x40 (64 bytes)
bInterval : 0x0
ENDPOINT 0x2: Bulk OUT ===============================
bLength : 0x7 (7 bytes)
bDescriptorType : 0x5 Endpoint
bEndpointAddress : 0x2 OUT
bmAttributes : 0x2 Bulk
wMaxPacketSize : 0x40 (64 bytes)
bInterval : 0x0
Added public GIST form my dymoprint version.
@aleksasp, that's excellent!!!
Do you need to run with sudo in order to print?
Also, do you know to what degree CUPS is necessary? My understanding is that dymoprint replaces CUPS in all cases.
I see what happened now with why this repo uses the HID virtual file while the original dymoprint uses PyUSB. The latest version of Sebastian Bronner's dymoprint was from 2016. However, the version on which this repo was based is from 2013. So unfortunately these improvements never got merged in our fork.
Printing without sudo. It is possibly likely due to appropriate rules file. Will asd that to the gist. Will try to remove CUPS or run the script on different host next week.
We should consider merging some of these changes, especially the use of PyUSB.
Changes added to the newer version of dymoprint
diff --git a/a b/b
index 1491c03..35f5c02 100644
--- a/a
+++ b/b
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# === LICENSE STATEMENT ===
# Copyright (c) 2011 Sebastian J. Bronner <waschtl@sbronner.com>
@@ -8,28 +8,14 @@
# this notice are preserved.
# === END LICENSE STATEMENT ===
-# On systems with access to sysfs under /sys, this script will use the three
-# variables DEV_CLASS, DEV_VENDOR, and DEV_PRODUCT to find the device file
-# under /dev automatically. This behavior can be overridden by setting the
-# variable DEV_NODE to the device file path. This is intended for cases, where
-# either sysfs is unavailable or unusable by this script for some reason.
-# Please beware that DEV_NODE must be set to None when not used, else you will
-# be bitten by the NameError exception.
-
-
-DEV_CLASS = 3
-DEV_VENDOR = 0x0922
-DEV_PRODUCT = 0x1002
-DEV_NODE = None
-DEV_NAME = 'Dymo LabelManager PnP'
-FONT_FILENAME = '/usr/share/fonts/truetype/ttf-bitstream-vera/Vera.ttf'
-FONT_SIZERATIO = 7./8
-VERSION = "0.1.0 (2013-07-09)"
+# The following module libraries are not included with python and need to be
+# installed separately:
+# * Pillow (python-pillow or python3-pillow)
+# * PyUSB (python-pyusb, python3-pyusb, python-usb, or python3-usb)
-import Image
-import ImageDraw
-import ImageFont
+from PIL import Image, ImageDraw, ImageFont
+import argparse
import array
import fcntl
import os
@@ -39,6 +25,22 @@ import subprocess
import sys
import termios
import textwrap
+import usb
+
+
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSans.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSans-Bold.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSans-Oblique.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSans-BoldOblique.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSans-ExtraLight.ttf'
+FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSansCondensed.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSansCondensed-Bold.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSansCondensed-Oblique.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/DejaVuSansCondensed-BoldOblique.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/ezra/SILEOT.ttf'
+#FONT_FILENAME = '/usr/share/fonts/TTF/gentium/Gentium-R.ttf' # IPA
+FONT_SIZERATIO = 7./8
+VERSION = "0.2.0 (2016-11-07)"
class DymoLabeler:
@@ -51,242 +53,247 @@ class DymoLabeler:
high-level functions for help. Each function is marked in its docstring
with 'HLF' or 'MLF' in parentheses.
"""
-
_ESC = 0x1b
_SYN = 0x16
_MAX_BYTES_PER_LINE = 8 # 64 pixels on a 12mm tape
-
- def __init__(self, dev):
+ _USB_VENDOR = 0x0922
+ _USB_PRODUCT = (0x1001, 0x1002)
+ # Number of commands to send before waiting for a response. This helps
+ # to avoid timeouts due to differences between data transfer and
+ # printer speeds. I added this because I kept getting "IOError: [Errno
+ # 110] Connection timed out" with long labels. Using dev.default_timeout
+ # (1000) and the transfer speeds available in the descriptors somewhere, a
+ # sensible timeout can also be calculated dynamically.
+ _SYNWAIT = 64
+
+ def __init__(self):
"""Initialize the LabelManager object. (HLF)"""
-
self.cmd = []
self.response = False
- self.bytesPerLine_ = None
- self.dotTab_ = 0
- self.dev = open(dev, 'r+')
-
- def sendCommand(self):
+ self.bytes_per_line_ = None
+ self.dot_tab_ = 0
+
+ # Find and prepare device communication endpoints.
+ dev = usb.core.find(custom_match = lambda d: (d.idVendor ==
+ self._USB_VENDOR and d.idProduct in self._USB_PRODUCT))
+ if dev is None:
+ raise RuntimeError("No USB device matching the following criteria "
+ "was found:\n idVendor: 0x%04x\n idProduct: %s" %
+ (self._USB_VENDOR, ', '.join('0x%04x' % id_ for id_ in
+ self._USB_PRODUCT)))
+ try:
+ dev.set_configuration()
+ except usb.core.USBError as e:
+ if e.errno == 13:
+ # Handle error number 13 (Access denied) by printing
+ # instructions for gaining access.
+ lines = []
+ lines.append("You do not have sufficient access to the "
+ "device. You probably want to add some udev rules in "
+ "/etc/udev/rules.d/dymoprint.rules along the following "
+ "lines:")
+ lines.append("")
+ for id_ in self._USB_PRODUCT:
+ lines.append('ACTION=="add", SUBSYSTEMS=="usb", '
+ 'ATTRS{idVendor}=="%04X", ATTRS{idProduct}=="%04X", '
+ 'MODE="0660", GROUP="users"' % (self._USB_VENDOR, id_))
+ lines.append("")
+ lines.append("Following that, turn your device off and back "
+ "on again to activate the new permissions.")
+ raise RuntimeError('\n'.join(lines))
+ if e.errno == 16:
+ # Handle error number 16 (Resource busy) by ignoring it. It
+ # just means that the configuration has already been set.
+ pass
+ else:
+ # On all other errors, the original exception is simple
+ # re-raised.
+ raise
+ intf = usb.util.find_descriptor(dev.get_active_configuration(),
+ bInterfaceClass=0x3)
+ if dev.is_kernel_driver_active(intf.bInterfaceNumber):
+ dev.detach_kernel_driver(intf.bInterfaceNumber)
+ self.data = usb.util.find_descriptor(intf, custom_match = (lambda e:
+ usb.util.endpoint_direction(e.bEndpointAddress) ==
+ usb.util.ENDPOINT_OUT))
+ self.status = usb.util.find_descriptor(intf, custom_match = (lambda e:
+ usb.util.endpoint_direction(e.bEndpointAddress) ==
+ usb.util.ENDPOINT_IN))
+
+ def send_command(self):
"""Send the already built command to the LabelManager. (MLF)"""
-
if len(self.cmd) == 0:
return
- cmdBin = array.array('B', self.cmd)
- cmdBin.tofile(self.dev)
+ while len(self.cmd) > 0:
+ synCount = 0
+ pos = -1
+ while synCount < self._SYNWAIT:
+ try:
+ pos += self.cmd[pos+1:].index(self._SYN) + 1
+ except ValueError:
+ pos = len(self.cmd)
+ break
+ synCount += 1
+ cmdBin = array.array('B', [self._ESC, ord('A')])
+ cmdBin.tofile(self.data)
+ rspBin = self.status.read(8)
+ rsp = array.array('B', rspBin).tolist()
+ print(rsp, pos, len(self.cmd))
+ cmdBin = array.array('B', self.cmd[:pos])
+ cmdBin.tofile(self.data)
+ self.cmd = self.cmd[pos:]
self.cmd = []
if not self.response:
return
self.response = False
- responseBin = self.dev.read(8)
+ responseBin = self.status.read(8)
response = array.array('B', responseBin).tolist()
return response
- def resetCommand(self):
+ def reset_command(self):
"""Remove a partially built command. (MLF)"""
-
self.cmd = []
self.response = False
- def buildCommand(self, cmd):
+ def build_command(self, cmd):
"""Add the next instruction to the command. (MLF)"""
-
self.cmd += cmd
- def statusRequest(self):
+ def status_request(self):
"""Set instruction to get the device's status. (MLF)"""
-
cmd = [self._ESC, ord('A')]
- self.buildCommand(cmd)
+ self.build_command(cmd)
self.response = True
- def dotTab(self, value):
+ def dot_tab(self, value):
"""Set the bias text height, in bytes. (MLF)"""
-
if value < 0 or value > self._MAX_BYTES_PER_LINE:
raise ValueError
cmd = [self._ESC, ord('B'), value]
- self.buildCommand(cmd)
- self.dotTab_ = value
- self.bytesPerLine_ = None
+ self.build_command(cmd)
+ self.dot_tab_ = value
+ self.bytes_per_line_ = None
- def tapeColor(self, value):
+ def tape_color(self, value):
"""Set the tape color. (MLF)"""
-
if value < 0: raise ValueError
cmd = [self._ESC, ord('C'), value]
- self.buildCommand(cmd)
+ self.build_command(cmd)
- def bytesPerLine(self, value):
+ def bytes_per_line(self, value):
"""Set the number of bytes sent in the following lines. (MLF)"""
-
- if value < 0 or value + self.dotTab_ > self._MAX_BYTES_PER_LINE:
+ if value < 0 or value + self.dot_tab_ > self._MAX_BYTES_PER_LINE:
raise ValueError
- if value == self.bytesPerLine_:
+ if value == self.bytes_per_line_:
return
cmd = [self._ESC, ord('D'), value]
- self.buildCommand(cmd)
- self.bytesPerLine_ = value
+ self.build_command(cmd)
+ self.bytes_per_line_ = value
def cut(self):
"""Set instruction to trigger cutting of the tape. (MLF)"""
-
cmd = [self._ESC, ord('E')]
- self.buildCommand(cmd)
+ self.build_command(cmd)
def line(self, value):
"""Set next printed line. (MLF)"""
-
- self.bytesPerLine(len(value))
+ self.bytes_per_line(len(value))
cmd = [self._SYN] + value
- self.buildCommand(cmd)
+ self.build_command(cmd)
- def chainMark(self):
+ def chain_mark(self):
"""Set Chain Mark. (MLF)"""
-
- self.dotTab(0)
- self.bytesPerLine(self._MAX_BYTES_PER_LINE)
+ self.dot_tab(0)
+ self.bytes_per_line(self._MAX_BYTES_PER_LINE)
self.line([0x99] * self._MAX_BYTES_PER_LINE)
- def skipLines(self, value):
+ def skip_lines(self, value):
"""Set number of lines of white to print. (MLF)"""
-
if value <= 0:
raise ValueError
- self.bytesPerLine(0)
+ self.bytes_per_line(0)
cmd = [self._SYN] * value
- self.buildCommand(cmd)
+ self.build_command(cmd)
- def initLabel(self):
+ def init_label(self):
"""Set the label initialization sequence. (MLF)"""
-
cmd = [0x00] * 8
- self.buildCommand(cmd)
+ self.build_command(cmd)
- def getStatus(self):
+ def get_status(self):
"""Ask for and return the device's status. (HLF)"""
+ self.status_request()
+ response = self.send_command()
+ print(response)
- self.statusRequest()
- response = self.sendCommand()
- print response
-
- def printLabel(self, lines, dotTab):
+ def print_label(self, lines, dot_tab):
"""Print the label described by lines. (HLF)"""
-
- self.initLabel
- self.tapeColor(0)
- self.dotTab(dotTab)
+ self.init_label
+ self.tape_color(0)
+ self.dot_tab(dot_tab)
for line in lines:
self.line(line)
- self.skipLines(56) # advance printed matter past cutter
- self.skipLines(56) # add symmetric margin
- self.statusRequest()
- response = self.sendCommand()
- print response
+ self.skip_lines(56) # advance printed matter past cutter
+ self.skip_lines(56) # add symmetric margin
+ self.status_request()
+ response = self.send_command()
+ print(response)
def die(message=None):
- if message: print >> sys.stderr, message
+ if message: print(message, file=sys.stderr)
sys.exit(1)
def pprint(par, fd=sys.stdout):
rows, columns = struct.unpack('HH', fcntl.ioctl(sys.stderr,
termios.TIOCGWINSZ, struct.pack('HH', 0, 0)))
- print >> fd, textwrap.fill(par, columns)
-
-
-def getDeviceFile(classID, vendorID, productID):
- # find file containing the device's major and minor numbers
- searchdir = '/sys/bus/hid/devices'
- pattern = '^%04d:%04X:%04X.[0-9A-F]{4}$' % (classID, vendorID, productID)
- deviceCandidates = os.listdir(searchdir)
- foundpath = None
- for devname in deviceCandidates:
- if re.match(pattern, devname):
- foundpath = os.path.join(searchdir, devname)
- break
- if not foundpath:
- return
- searchdir = os.path.join(foundpath, 'hidraw')
- devname = os.listdir(searchdir)[0]
- foundpath = os.path.join(searchdir, devname)
- filepath = os.path.join(foundpath, 'dev')
-
- # get the major and minor numbers
- f = open(filepath, 'r')
- devnums = [int(n) for n in f.readline().strip().split(':')]
- f.close()
- devnum = os.makedev(devnums[0], devnums[1])
-
- # check if a symlink with the major and minor numbers is available
- filepath = '/dev/char/%d:%d' % (devnums[0], devnums[1])
- if os.path.exists(filepath):
- return os.path.realpath(filepath)
-
- # check if the relevant sysfs path component matches a file name in
- # /dev, that has the proper major and minor numbers
- filepath = os.path.join('/dev', devname)
- if os.stat(filepath).st_rdev == devnum:
- return filepath
-
- # search for a device file with the proper major and minor numbers
- for dirpath, dirnames, filenames in os.walk('/dev'):
- for filename in filenames:
- filepath = os.path.join(dirpath, filename)
- if os.stat(filepath).st_rdev == devnum:
- return filepath
-
-
-def access_error(dev):
- pprint('You do not have sufficient access to the device file %s:' % dev,
- sys.stderr)
- subprocess.call(['ls', '-l', dev], stdout=sys.stderr)
- print >> sys.stderr
- pprint('You probably want to add a rule in /etc/udev/rules.d along the '
- 'following lines:', sys.stderr)
- print >> sys.stderr, 'SUBSYSTEM=="hidraw", ACTION=="add", ATTRS{idVendor}=="%04X", ATTRS{idProduct}=="%04X", GROUP="plugdev"' % (DEV_VENDOR, DEV_PRODUCT)
- print >> sys.stderr
- pprint('Following that, turn off your device and back on again to '
- 'activate the new permissions.', sys.stderr)
+ print(textwrap.fill(par, columns), file=fd)
def main():
- # get device file name
- if not DEV_NODE:
- dev = getDeviceFile(DEV_CLASS, DEV_VENDOR, DEV_PRODUCT)
- else:
- dev = DEV_NODE
- if not dev:
- die("The device '%s' could not be found on this system." % DEV_NAME)
-
+ # set up argument parsing with usage and help output
+ description = ("This script will print labels on a Dymo LabelManager PnP "
+ "connected to your computer via USB.")
+ parser = argparse.ArgumentParser(
+ description=description)
+ parser.add_argument(
+ '-v', '--version',
+ action='version',
+ version='%(prog)s ' + VERSION
+ )
+ parser.add_argument(
+ 'lines',
+ metavar='line',
+ nargs='+',
+ help=("A single line will be printed at the maximum available size on "
+ "the label. If multiple lines are specified, they will be reduced "
+ "in size to fit the vertical space and will be placed in a "
+ "column."))
+ args = parser.parse_args()
+
# create dymo labeler object
- try:
- lm = DymoLabeler(dev)
- except IOError:
- die(access_error(dev))
-
- # check for any text specified on the command line
- labeltext = [arg.decode(sys.stdin.encoding) for arg in sys.argv[1:]]
- if len(labeltext) == 0: die("No label text was specified.")
+ lm = DymoLabeler()
# create an empty label image
labelheight = lm._MAX_BYTES_PER_LINE * 8
- lineheight = float(labelheight) / len(labeltext)
+ lineheight = float(labelheight) / len(args.lines)
fontsize = int(round(lineheight * FONT_SIZERATIO))
font = ImageFont.truetype(FONT_FILENAME, fontsize)
- labelwidth = max(font.getsize(line)[0] for line in labeltext)
+ labelwidth = max(font.getsize(line)[0] for line in args.lines)
labelbitmap = Image.new('1', (labelwidth, labelheight))
# write the text into the empty image
labeldraw = ImageDraw.Draw(labelbitmap)
- for i, line in enumerate(labeltext):
+ for i, line in enumerate(args.lines):
lineposition = int(round(i * lineheight))
labeldraw.text((0, lineposition), line, font=font, fill=255)
del labeldraw
# convert the image to the proper matrix for the dymo labeler object
labelrotated = labelbitmap.transpose(Image.ROTATE_270)
- labelstream = labelrotated.tostring()
- labelstreamrowlength = labelheight/8 + (1 if labelheight%8 != 0 else 0)
+ labelstream = labelrotated.tobytes()
+ labelstreamrowlength = labelheight//8 + (1 if labelheight%8 != 0 else 0)
if len(labelstream)/labelstreamrowlength != labelwidth:
die('An internal problem was encountered while processing the label '
'bitmap!')
@@ -305,7 +312,7 @@ def main():
del line[-1]
# print the label
- lm.printLabel(labelmatrix, dottab)
+ lm.print_label(labelmatrix, dottab)
if __name__ == '__main__':
@@ -319,3 +326,10 @@ if __name__ == '__main__':
# * allow font size specification with command line option (points, pixels?)
# * provide an option to show a preview of what the label will look like
# * read and write a .dymoprint file containing user preferences
+# * implement errors using exceptions
+# * implement regular output using standard methods
+# * get rid of die()
+# * get rid of pprint()
+# * get rid of access_error()
+# * provide a version that includes its dependent libraries, and a version that
+# uses system libraries
I wonder if we still need usbmodeswitch? It's a bit of a hassle to configure it, so it would be great to do without it (#41).
Excellent!!!
Any chance you could make a PR for this repo? Then we could officially support the 280. ๐
I wonder if this would make it feasible to run this on other operating systems as well...
Just to let you know: Also haveing a LabelManager 280 and it works perfectly fine! Regarding the privileges: The device was detected as /dev/usb/lp1 with owner root and group lp. Since I'm member of that lp group on my PC it directly worked out of the box.
Just to let you know: Also haveing a LabelManager 280 and it works perfectly fine! Regarding the privileges: The device was detected as /dev/usb/lp1 with owner root and group lp. Since I'm member of that lp group on my PC it directly worked out of the box.
"it" means this dymoprint repo as is? or one of the patches mentioned above? thanks!
@srl295, there have been some substantial changes to the code since that post. The version at that time was 1.3.0. If you're having trouble you can add more details and/or try downgrading with
pipx install --force dymoprint==1.3.0
My expectation would be that it runs for the first time with a permissions error, but the error message should suggest the command to run to fix the permissions error, and then it should work the second time.
It'd be helpful to know the output you're seeing.
@maresb worked perfectly
You do not have sufficient access to the device. You probably want to add the a udev rule in /etc/udev/rules.d with the following command:
echo 'ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0922", ATTRS{idProduct}=="1005", MODE="0666"' | sudo tee /etc/udev/rules.d/91-dymo-1005.rules
Next refresh udev with:
sudo udevadm control --reload-rules
sudo udevadm trigger --attr-match=idVendor="0922"
Finally, turn your device off and back on again to activate the new permissions.
If this still does not resolve the problem, you might need to reboot. In case rebooting is necessary, please report this at <https://github.com/computerlyrik/dymoprint/pull/56>. We are still trying to figure out a simple procedure which works for everyone. In case you still cannot connect, or if you have any information or ideas, please post them at that link.
i'd recommend closing this as fixed.