I've been fooling around with a Symbol LS2208 barcode scanner attached to a CentOS 7 machine as part of a network automation project. I learned a bit about the scanner, udev and systemd along the way.
The LS2208

The LS2208 gets configured by scanning barcodes. Special codes found in the manual. So far, the ones I've found most interesting are:
- Set Factory Defaults
- Simple COM Port Emulation
- Low Volume
- Beep on <BEL> (still need to fool with this - seems like it could provide useful feedback to the operator)
- Do Not Beep After Good Decode
By default the scanner appears with USB vendor/product codes 0x05e0/0x1200 which makes it emulate a USB keyboard. Fun to play with, but not how I want it to work for my project.
Scanning the Simple COM Port Emulation barcode found in the manual changes its USB personality to 0x05e0/0x0600, so that it shows up as an hidraw Linux device. I can't find any indication that it's a serial port, though the symbolserial device driver loads in CentOS 7.
With each scan, the device at /dev/hidrawX produces 64 bytes consisting of:
- A single byte indicating the length of the scanned data. UPC codes, for example, are 12 bytes long so they always produce 0x0C as the first byte.
- The scanned data
- Padding with <NUL> characters up to 64 bytes in total
We can take a quick look at the scan data with: od -t x1 < /dev/hidraw0.
A tiny bit of python
This little program reads from the scanner and spits out the result:
#!/usr/bin/env python
import argparse
import sys
defaultDevice = '/dev/scanner'
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--dev', dest='d', default=defaultDevice,
help='Scanner device; defaults to '+defaultDevice)
parser.add_argument('-o', '--out', dest='o', help='Output file')
args = parser.parse_args()
inDev = open(args.d, 'rb')
inDev.flush()
if args.o:
outFile = open(args.o, 'a')
else:
outFile = sys.stdout
while True:
inBytes = inDev.read(64)
length = ord(inBytes[:1])
value = inBytes[1:length+1]
outFile.write(str(length)+"\t"+value+"\n")
outFile.flush()
udev
Not wanting to count on the scanner always landing on /dev/hidraw0, the next task was to write a udev rule to make it easy to find. I wound up with the following in /etc/udev/rules.d/10-scanner.rules:
Essentially, I'm matching hidraw devices with the appropriate vendor and product codes, setting the resulting device file to be owned by group barcode (a group I created for use by the scanner project) and creating a symlink: /dev/scanner -> /dev/hidraw0
When the device appears, udev will ask systemd will kick off the BarcodeScanner unit with an argument (%N in the udev rule) indicating the path to the scanner device.
systemd
Finally, we need the systemd unit file to go with that udev rule. Here's my /etc/systemd/system/BarcodeScanner@.service unit file:
The StopWhenUnneeded directive causes systemd to kill (SIGTERM, then SIGKILL if needed) the scan.py loop when nothing "wants" it. The %I evaluates to the device file passed in by udev.
Type=simple tells systemd that it shouldn't expect this unit to properly daemonize itself.
Poke systemd so that it notices the new unit file:
Now, when the scanner's USB cable is plugged in, the python code starts up and gets pointed (-d argument) at the scanner to start logging bar codes to /tmp/scanned.txt. When the scanner is unplugged, the BarcodeScanner unit is no longer "wanted" by anything, so systemd kills it off.
KERNEL=="hidraw[0-9]*", ATTRS{idVendor}=="05e0", ATTRS{idProduct}=="0600", GROUP="barcode", ACTION=="add", SYMLINK="scanner", TAG+="systemd", ENV{SYSTEMD_WANTS}="BarcodeScanner@%N.service"
Essentially, I'm matching hidraw devices with the appropriate vendor and product codes, setting the resulting device file to be owned by group barcode (a group I created for use by the scanner project) and creating a symlink: /dev/scanner -> /dev/hidraw0
When the device appears, udev will ask systemd will kick off the BarcodeScanner unit with an argument (%N in the udev rule) indicating the path to the scanner device.
systemd
Finally, we need the systemd unit file to go with that udev rule. Here's my /etc/systemd/system/BarcodeScanner@.service unit file:
[Unit]
Description=Simple Symbol Scanner
StopWhenUnneeded=yes
[Service]
Type=simple
ExecStart=/tmp/scan.py -d %I -o /tmp/scanned.txt
[Install]
WantedBy=multi-user.target
The StopWhenUnneeded directive causes systemd to kill (SIGTERM, then SIGKILL if needed) the scan.py loop when nothing "wants" it. The %I evaluates to the device file passed in by udev.
Type=simple tells systemd that it shouldn't expect this unit to properly daemonize itself.
Poke systemd so that it notices the new unit file:
sudo systemctl daemon-reload
Now, when the scanner's USB cable is plugged in, the python code starts up and gets pointed (-d argument) at the scanner to start logging bar codes to /tmp/scanned.txt. When the scanner is unplugged, the BarcodeScanner unit is no longer "wanted" by anything, so systemd kills it off.