平时用的最多的扫描枪通常只是一个简单的输入设备(好比键盘,鼠标), 另一头需要连接电脑, 用的 usb 或者串口. 扫描枪负责识别条码, 电脑收到后执行业务的逻辑. 有时候只是简单的数据采集工作, 数据传到服务器云端, 放台电脑在那儿显得浪费而且需要昂贵的维护. 而小巧灵活而且价格低廉的树莓派同学马上举手说: “我可以!”
可能你已经想到了, 用图形界面不是很 easy 吗? 是的, 但不是最好的办法. 即使目前最新版树莓派 4B 的性能对于图形界面来说只能算得上及格. 我们的目标是后台进程接受录入, 开机即用.

实验用到的设备

带 USB 接收器的条码扫描枪
image.png
树莓派 4b
image.png
我们将用到 python-evdev 程序库, 封装了对 usb 设备的读写操作. 官网 https://python-evdev.readthedocs.io/

安装 evdev

  1. sudo pip install evdev

编写代码
简单几行代码检测 usb 设备:

  1. #!/usr/bin/python3
  2. import evdev
  3. # 列出 usb 设备
  4. devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
  5. print('发现设备: ')
  6. for device in devices:
  7. print(device.path, device.name, device.phys)

执行结果:

  1. 发现设备:
  2. /dev/input/event0 Netum. HIDKB usb-0000:01:00.0-1.3/input0

显示成功读到了扫描枪设备. 接下来要读取扫描枪的输入了. 修改前面的代码

  1. #!/usr/bin/python3
  2. import asyncio, evdev
  3. # 检测到输入时触发
  4. async def print_events(device):
  5. async for event in device.async_read_loop():
  6. print(device.path, evdev.categorize(event), sep=': ')
  7. # 列出 usb 设备
  8. devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
  9. print('发现以下设备: ')
  10. for device in devices:
  11. print(device.path, device.name, device.phys)
  12. for device in devices:
  13. # 用 asyncio 同时接受多个设备的录入
  14. asyncio.ensure_future(print_events(device))
  15. loop = asyncio.get_event_loop()
  16. loop.run_forever()

为了兼容多个设备的输入用到了 asyncio, 异步执行单个设备的事件循环, 这样就不会因为一个设备的输入 block 住其他设备.
运行看看效果:

  1. 发现以下设备:
  2. /dev/input/event0 Netum. HIDKB usb-0000:01:00.0-1.3/input0
  3. /dev/input/event0: event at 1584874459.655187, code 00, type 17, val 01
  4. /dev/input/event0: event at 1584874459.655187, code 04, type 04, val 458787
  5. /dev/input/event0: key event at 1584874459.655187, 7 (KEY_6), down
  6. /dev/input/event0: synchronization event at 1584874459.655187, SYN_REPORT
  7. /dev/input/event0: event at 1584874459.656157, code 04, type 04, val 458787
  8. /dev/input/event0: key event at 1584874459.656157, 7 (KEY_6), up
  9. /dev/input/event0: synchronization event at 1584874459.656157, SYN_REPORT
  10. /dev/input/event0: event at 1584874459.657169, code 04, type 04, val 458790
  11. /dev/input/event0: key event at 1584874459.657169, 10 (KEY_9), down
  12. /dev/input/event0: synchronization event at 1584874459.657169, SYN_REPORT
  13. /dev/input/event0: event at 1584874459.658158, code 04, type 04, val 458790
  14. /dev/input/event0: key event at 1584874459.658158, 10 (KEY_9), up
  15. ………….

把每个按键的详细事件打印出来了. 说明成功接收到输入了. 还差一步, 把按键的内容转换成字符串. 继续修改代码:

  1. #!/usr/bin/python3
  2. import asyncio, evdev
  3. # 按键转字符表 只列出了常用的字符
  4. keymap = {
  5. 'KEY_0': u'0',
  6. 'KEY_1': u'1',
  7. 'KEY_2': u'2',
  8. 'KEY_3': u'3',
  9. 'KEY_4': u'4',
  10. 'KEY_5': u'5',
  11. 'KEY_6': u'6',
  12. 'KEY_7': u'7',
  13. 'KEY_8': u'8',
  14. 'KEY_9': u'9',
  15. 'KEY_A': u'A',
  16. 'KEY_B': u'B',
  17. 'KEY_C': u'C',
  18. 'KEY_D': u'D',
  19. 'KEY_E': u'E',
  20. 'KEY_F': u'F',
  21. 'KEY_G': u'G',
  22. 'KEY_H': u'H',
  23. 'KEY_I': u'I',
  24. 'KEY_J': u'J',
  25. 'KEY_K': u'K',
  26. 'KEY_L': u'L',
  27. 'KEY_M': u'M',
  28. 'KEY_N': u'N',
  29. 'KEY_O': u'O',
  30. 'KEY_P': u'P',
  31. 'KEY_Q': u'Q',
  32. 'KEY_R': u'R',
  33. 'KEY_S': u'S',
  34. 'KEY_T': u'T',
  35. 'KEY_U': u'U',
  36. 'KEY_V': u'V',
  37. 'KEY_W': u'W',
  38. 'KEY_X': u'X',
  39. 'KEY_Y': u'Y',
  40. 'KEY_Z': u'Z',
  41. 'KEY_TAB': u'\t',
  42. 'KEY_SPACE': u' ',
  43. 'KEY_COMMA': u',',
  44. 'KEY_SEMICOLON': u';',
  45. 'KEY_EQUAL': u'=',
  46. 'KEY_LEFTBRACE': u'[',
  47. 'KEY_RIGHTBRACE': u']',
  48. 'KEY_MINUS': u'-',
  49. 'KEY_APOSTROPHE': u'\'',
  50. 'KEY_GRAVE': u'`',
  51. 'KEY_DOT': u'.',
  52. 'KEY_SLASH': u'/',
  53. 'KEY_BACKSLASH': u'\\',
  54. 'KEY_ENTER': u'\n',
  55. }
  56. # 检测到输入时触发
  57. async def print_events(device):
  58. buf = ''
  59. async for event in device.async_read_loop():
  60. # key_up= 0 key_down= 1 key_hold= 2
  61. if event.type == evdev.ecodes.EV_KEY and event.value == 1:
  62. kv = evdev.events.KeyEvent(event)
  63. # 本次修改的地方, 把事件映射到字符表
  64. if (kv.scancode == evdev.ecodes.KEY_ENTER):
  65. print('读到输入: ', buf)
  66. '''
  67. 业务逻辑
  68. '''
  69. # 清空 buffer
  70. buf = ''
  71. else:
  72. buf += keymap.get(kv.keycode)
  73. devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
  74. print('发现以下设备: ')
  75. for device in devices:
  76. print(device.path, device.name, device.phys)
  77. for device in devices:
  78. asyncio.ensure_future(print_events(device))
  79. loop = asyncio.get_event_loop()
  80. loop.run_forever()

再来运行代码, 顺利拿到条码内容了.
image.png
增加点难度, 这次测试还加入了键盘, 效果棒棒哒.
以上实验全部代码都在 https://gitee.com/csling/ban-scanner

写在最后

如果你想把这个用到实际生产中, 然而还有许多问题要考虑, 比如动态添加和移除 usb 设备. 了解更多请关注我的公众号, 也欢迎留下你的想法.