qrcode_reader.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. TIMEOUT = 3
  30. logger = logging.getLogger('qrcode_reader')
  31. logging.basicConfig(level=logging.INFO)
  32. logging.getLogger('requests').setLevel(logging.WARNING)
  33. logging.getLogger('urllib3').setLevel(logging.WARNING)
  34. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  35. def convert_obj(obj):
  36. try:
  37. return int(obj)
  38. except:
  39. pass
  40. if isinstance(obj, str):
  41. obj_l = obj.lower()
  42. if obj_l in ['true', 'on', 'yes']:
  43. return True
  44. elif obj_l in ['false', 'off', 'no']:
  45. return False
  46. elif obj == '%NOW%':
  47. return str(datetime.datetime.utcnow())
  48. return obj
  49. def convert(seq):
  50. if isinstance(seq, dict):
  51. d = {}
  52. for key, item in seq.items():
  53. d[key] = convert(item)
  54. return d
  55. if isinstance(seq, (list, tuple)):
  56. return [convert(x) for x in seq]
  57. return convert_obj(seq)
  58. class ImprovedEncoder(json.JSONEncoder):
  59. """Enhance the default JSON encoder to serialize datetime instances."""
  60. def default(self, o):
  61. if isinstance(o, (datetime.datetime, datetime.date,
  62. datetime.time, datetime.timedelta)):
  63. try:
  64. return str(o)
  65. except Exception:
  66. pass
  67. elif isinstance(o, set):
  68. return list(o)
  69. return json.JSONEncoder.default(self, o)
  70. # Inject our class as the default encoder.
  71. json._default_encoder = ImprovedEncoder()
  72. class Connector():
  73. def __init__(self, cfg):
  74. self.cfg = cfg
  75. self.session = None
  76. self.url = cfg['eventman']['url']
  77. self.login_url = urllib.parse.urljoin(self.url, '/v1.0/login')
  78. self.checkin_url = urllib.parse.urljoin(self.url, os.path.join('/v1.0/events/',
  79. cfg['event']['id'], 'tickets/'))
  80. self.login()
  81. def login(self):
  82. logger.debug('connecting to eventman at %s' % self.login_url)
  83. self.session = requests.Session()
  84. self.session.verify = False
  85. ca = cfg['eventman'].get('ca')
  86. if ca and os.path.isfile(ca):
  87. self.session.verify = ca
  88. username = cfg['eventman'].get('username')
  89. password = cfg['eventman'].get('password')
  90. params = {}
  91. if username:
  92. params['username'] = username
  93. if password:
  94. params['password'] = password
  95. req = self.session.post(self.login_url, json=params, timeout=TIMEOUT)
  96. req.raise_for_status()
  97. req.connection.close()
  98. logger.info('connection to eventman at %s established' % self.login_url)
  99. def checkin(self, code):
  100. msg = 'scanning code %s: ' % code
  101. limit_field = None
  102. try:
  103. limit_field = self.cfg['event'].getint('limit_field')
  104. except:
  105. pass
  106. if limit_field:
  107. code = code[:limit_field]
  108. _searchFor = '%s:%s' % (cfg['event']['field'], code)
  109. params = {cfg['event']['field']: code, '_errorMessage': 'code: %s' % code, '_searchFor': _searchFor}
  110. checkin_url = self.checkin_url + '?' + urllib.parse.urlencode(params)
  111. json = convert(dict(self.cfg['actions']))
  112. req = self.session.put(checkin_url, json=json, timeout=TIMEOUT)
  113. error = False
  114. try:
  115. req.raise_for_status()
  116. msg += 'ok'
  117. except requests.exceptions.HTTPError:
  118. error = True
  119. msg += 'error: %s' % req.json().get('message')
  120. if not error:
  121. logger.info(msg)
  122. else:
  123. logger.warning(msg)
  124. req.connection.close()
  125. def scan(port):
  126. retry = 1
  127. logger.info('trying to connect to %s, please wait...' % port)
  128. while True:
  129. logger.debug('waiting for connection on port %s...' % port)
  130. try:
  131. ser = serial.Serial(port=port, timeout=1)
  132. break
  133. except serial.serialutil.SerialException as ex:
  134. if retry >= 20:
  135. logger.error('unable to connect: %s' % ex)
  136. sys.exit(2)
  137. time.sleep(1)
  138. retry += 1
  139. logger.info('connected to %s' % port)
  140. ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), newline='\r', line_buffering=True)
  141. while True:
  142. try:
  143. line = ser_io.readline().strip()
  144. except serial.serialutil.SerialException as ex:
  145. logger.error('disconnected: %s' % ex)
  146. sys.exit(3)
  147. if not line:
  148. continue
  149. yield line
  150. if __name__ == '__main__':
  151. parser = argparse.ArgumentParser()
  152. parser.add_argument('-c', '--code', help='specify a single code', action='store')
  153. parser.add_argument('--config', help='user a different configuration file (default: qrcode_reader.ini)',
  154. action='store', default='qrcode_reader.ini')
  155. args = parser.parse_args()
  156. cfg = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
  157. cfg.read(args.config)
  158. if cfg['qrcode_reader'].getboolean('debug'):
  159. logger.setLevel(logging.DEBUG)
  160. try:
  161. connector = Connector(cfg)
  162. except Exception as ex:
  163. logger.error('unable to connect to %s: %s' % (cfg['eventman']['url'], ex))
  164. sys.exit(1)
  165. if args.code:
  166. connector.checkin(args.code)
  167. else:
  168. try:
  169. for code in scan(port=cfg['connection']['port']):
  170. try:
  171. connector.checkin(code)
  172. except Exception as ex:
  173. logger.error('error at checkin for code %s: %s', code, ex)
  174. except KeyboardInterrupt:
  175. logger.info('exiting...')