平时用的最多的扫描枪通常只是一个简单的输入设备(好比键盘,鼠标), 另一头需要连接电脑, 用的 usb 或者串口. 扫描枪负责识别条码, 电脑收到后执行业务的逻辑. 有时候只是简单的数据采集工作, 数据传到服务器云端, 放台电脑在那儿显得浪费而且需要昂贵的维护. 而小巧灵活而且价格低廉的树莓派同学马上举手说: “我可以!”
可能你已经想到了, 用图形界面不是很 easy 吗? 是的, 但不是最好的办法. 即使目前最新版树莓派 4B 的性能对于图形界面来说只能算得上及格. 我们的目标是后台进程接受录入, 开机即用.
实验用到的设备
带 USB 接收器的条码扫描枪 
树莓派 4b
我们将用到 python-evdev 程序库, 封装了对 usb 设备的读写操作. 官网 https://python-evdev.readthedocs.io/
安装 evdev
sudo pip install evdev
编写代码
简单几行代码检测 usb 设备:
#!/usr/bin/python3import evdev# 列出 usb 设备devices = [evdev.InputDevice(path) for path in evdev.list_devices()]print('发现设备: ')for device in devices:print(device.path, device.name, device.phys)
执行结果:
发现设备:/dev/input/event0 Netum. HIDKB usb-0000:01:00.0-1.3/input0
显示成功读到了扫描枪设备. 接下来要读取扫描枪的输入了. 修改前面的代码
#!/usr/bin/python3import asyncio, evdev# 检测到输入时触发async def print_events(device):async for event in device.async_read_loop():print(device.path, evdev.categorize(event), sep=': ')# 列出 usb 设备devices = [evdev.InputDevice(path) for path in evdev.list_devices()]print('发现以下设备: ')for device in devices:print(device.path, device.name, device.phys)for device in devices:# 用 asyncio 同时接受多个设备的录入asyncio.ensure_future(print_events(device))loop = asyncio.get_event_loop()loop.run_forever()
为了兼容多个设备的输入用到了 asyncio, 异步执行单个设备的事件循环, 这样就不会因为一个设备的输入 block 住其他设备.
运行看看效果:
发现以下设备:/dev/input/event0 Netum. HIDKB usb-0000:01:00.0-1.3/input0/dev/input/event0: event at 1584874459.655187, code 00, type 17, val 01/dev/input/event0: event at 1584874459.655187, code 04, type 04, val 458787/dev/input/event0: key event at 1584874459.655187, 7 (KEY_6), down/dev/input/event0: synchronization event at 1584874459.655187, SYN_REPORT/dev/input/event0: event at 1584874459.656157, code 04, type 04, val 458787/dev/input/event0: key event at 1584874459.656157, 7 (KEY_6), up/dev/input/event0: synchronization event at 1584874459.656157, SYN_REPORT/dev/input/event0: event at 1584874459.657169, code 04, type 04, val 458790/dev/input/event0: key event at 1584874459.657169, 10 (KEY_9), down/dev/input/event0: synchronization event at 1584874459.657169, SYN_REPORT/dev/input/event0: event at 1584874459.658158, code 04, type 04, val 458790/dev/input/event0: key event at 1584874459.658158, 10 (KEY_9), up………….
把每个按键的详细事件打印出来了. 说明成功接收到输入了. 还差一步, 把按键的内容转换成字符串. 继续修改代码:
#!/usr/bin/python3import asyncio, evdev# 按键转字符表 只列出了常用的字符keymap = {'KEY_0': u'0','KEY_1': u'1','KEY_2': u'2','KEY_3': u'3','KEY_4': u'4','KEY_5': u'5','KEY_6': u'6','KEY_7': u'7','KEY_8': u'8','KEY_9': u'9','KEY_A': u'A','KEY_B': u'B','KEY_C': u'C','KEY_D': u'D','KEY_E': u'E','KEY_F': u'F','KEY_G': u'G','KEY_H': u'H','KEY_I': u'I','KEY_J': u'J','KEY_K': u'K','KEY_L': u'L','KEY_M': u'M','KEY_N': u'N','KEY_O': u'O','KEY_P': u'P','KEY_Q': u'Q','KEY_R': u'R','KEY_S': u'S','KEY_T': u'T','KEY_U': u'U','KEY_V': u'V','KEY_W': u'W','KEY_X': u'X','KEY_Y': u'Y','KEY_Z': u'Z','KEY_TAB': u'\t','KEY_SPACE': u' ','KEY_COMMA': u',','KEY_SEMICOLON': u';','KEY_EQUAL': u'=','KEY_LEFTBRACE': u'[','KEY_RIGHTBRACE': u']','KEY_MINUS': u'-','KEY_APOSTROPHE': u'\'','KEY_GRAVE': u'`','KEY_DOT': u'.','KEY_SLASH': u'/','KEY_BACKSLASH': u'\\','KEY_ENTER': u'\n',}# 检测到输入时触发async def print_events(device):buf = ''async for event in device.async_read_loop():# key_up= 0 key_down= 1 key_hold= 2if event.type == evdev.ecodes.EV_KEY and event.value == 1:kv = evdev.events.KeyEvent(event)# 本次修改的地方, 把事件映射到字符表if (kv.scancode == evdev.ecodes.KEY_ENTER):print('读到输入: ', buf)'''业务逻辑'''# 清空 bufferbuf = ''else:buf += keymap.get(kv.keycode)devices = [evdev.InputDevice(path) for path in evdev.list_devices()]print('发现以下设备: ')for device in devices:print(device.path, device.name, device.phys)for device in devices:asyncio.ensure_future(print_events(device))loop = asyncio.get_event_loop()loop.run_forever()
再来运行代码, 顺利拿到条码内容了.
增加点难度, 这次测试还加入了键盘, 效果棒棒哒.
以上实验全部代码都在 https://gitee.com/csling/ban-scanner
写在最后
如果你想把这个用到实际生产中, 然而还有许多问题要考虑, 比如动态添加和移除 usb 设备. 了解更多请关注我的公众号, 也欢迎留下你的想法.
