qrcode_reader.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """qrcode_reader
  4. Scan the output of a serial QR Code reader.
  5. Copyright 2017 Davide Alberani <da@erlug.linux.it>
  6. RaspiBO <info@raspibo.org>
  7. Licensed under the Apache License, Version 2.0 (the "License");
  8. you may not use this file except in compliance with the License.
  9. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing, software
  11. distributed under the License is distributed on an "AS IS" BASIS,
  12. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. See the License for the specific language governing permissions and
  14. limitations under the License.
  15. """
  16. import os
  17. import io
  18. import sys
  19. import time
  20. import json
  21. import serial
  22. import urllib
  23. import logging
  24. import argparse
  25. import datetime
  26. import requests
  27. import configparser
  28. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  29. logger = logging.getLogger('qrcode_reader')
  30. logging.basicConfig(level=logging.INFO)
  31. logging.getLogger('requests').setLevel(logging.WARNING)
  32. logging.getLogger('urllib3').setLevel(logging.WARNING)
  33. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  34. def convert_obj(obj):
  35. try:
  36. return int(obj)
  37. except:
  38. pass
  39. if isinstance(obj, str):
  40. obj_l = obj.lower()
  41. if obj_l in ['true', 'on', 'yes']:
  42. return True
  43. elif obj_l in ['false', 'off', 'no']:
  44. return False
  45. elif obj == '%NOW%':
  46. return datetime.datetime.utcnow()
  47. return obj
  48. def convert(seq):
  49. if isinstance(seq, dict):
  50. d = {}
  51. for key, item in seq.items():
  52. d[key] = convert(item)
  53. return d
  54. if isinstance(seq, (list, tuple)):
  55. return [convert(x) for x in seq]
  56. return convert_obj(seq)
  57. class ImprovedEncoder(json.JSONEncoder):
  58. """Enhance the default JSON encoder to serialize datetime instances."""
  59. def default(self, o):
  60. if isinstance(o, (datetime.datetime, datetime.date,
  61. datetime.time, datetime.timedelta)):
  62. try:
  63. return str(o)
  64. except Exception as e:
  65. pass
  66. elif isinstance(o, set):
  67. return list(o)
  68. return json.JSONEncoder.default(self, o)
  69. # Inject our class as the default encoder.
  70. json._default_encoder = ImprovedEncoder()
  71. class Connector():
  72. def __init__(self, cfg):
  73. self.cfg = cfg
  74. self.session = None
  75. self.url = cfg['eventman']['url']
  76. self.login_url = urllib.parse.urljoin(self.url, '/v1.0/login')
  77. self.checkin_url = urllib.parse.urljoin(self.url, os.path.join('/v1.0/events/',
  78. cfg['event']['id'], 'tickets/'))
  79. self.login()
  80. def login(self):
  81. try:
  82. self.session = requests.Session()
  83. self.session.verify = False
  84. ca = cfg['eventman'].get('ca')
  85. if ca and os.path.isfile(ca):
  86. self.session.verify = ca
  87. username = cfg['eventman'].get('username')
  88. password = cfg['eventman'].get('password')
  89. params = {}
  90. if username:
  91. params['username'] = username
  92. if password:
  93. params['password'] = password
  94. req = self.session.post(self.login_url, json=params)
  95. req.raise_for_status()
  96. req.connection.close()
  97. except requests.exceptions.ConnectionError as ex:
  98. logger.error('unable to connect to %s: %s' % (self.login_url, ex))
  99. sys.exit(1)
  100. def checkin(self, code):
  101. msg = 'scanning code %s: ' % code
  102. limit_field = self.cfg['event'].getint('limit_field')
  103. if limit_field:
  104. code = code[:limit_field]
  105. params = {cfg['event']['field']: code, '_errorMessage': 'code: %s' % code}
  106. checkin_url = self.checkin_url + '?' + urllib.parse.urlencode(params)
  107. json = convert(dict(self.cfg['actions']))
  108. req = self.session.put(checkin_url, json=json)
  109. error = False
  110. try:
  111. req.raise_for_status()
  112. msg += 'ok'
  113. except requests.exceptions.HTTPError as ex:
  114. error = True
  115. msg += 'error: %s' % req.json().get('message')
  116. if not error:
  117. logger.info(msg)
  118. else:
  119. logger.warning(msg)
  120. req.connection.close()
  121. def scan(port):
  122. retry = 1
  123. logger.info('trying to connect to %s, please wait...' % port)
  124. while True:
  125. logger.debug('waiting for connection on port %s...' % port)
  126. try:
  127. ser = serial.Serial(port=port, timeout=1)
  128. break
  129. except serial.serialutil.SerialException as ex:
  130. if retry >= 20:
  131. logger.error('unable to connect: %s' % ex)
  132. sys.exit(2)
  133. time.sleep(1)
  134. retry += 1
  135. logger.info('connected to %s' % port)
  136. ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), newline='\r', line_buffering=True)
  137. while True:
  138. try:
  139. line = ser_io.readline().strip()
  140. except serial.serialutil.SerialException as ex:
  141. logger.error('disconnected: %s' % ex)
  142. sys.exit(3)
  143. if not line:
  144. continue
  145. yield line
  146. if __name__ == '__main__':
  147. parser = argparse.ArgumentParser()
  148. parser.add_argument('-c', '--code', help='specify a single code', action='store')
  149. parser.add_argument('--config', help='user a different configuration file (default: qrcode_reader.ini)',
  150. action='store', default='qrcode_reader.ini')
  151. args = parser.parse_args()
  152. cfg = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
  153. cfg.read(args.config)
  154. if cfg['qrcode_reader'].getboolean('debug'):
  155. logging.basicConfig(level=logging.DEBUG)
  156. connector = Connector(cfg)
  157. if args.code:
  158. connector.checkin(args.code)
  159. else:
  160. try:
  161. for code in scan(port=cfg['connection']['port']):
  162. connector.checkin(code)
  163. except KeyboardInterrupt:
  164. logger.info('exiting...')