piufared 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #!/usr/bin/python3
  2. #
  3. # Copyright (c) 2021 Clementine Computing LLC.
  4. #
  5. # This file is part of PopuFare.
  6. #
  7. # PopuFare is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # PopuFare is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with PopuFare. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. import serial
  21. import re
  22. import math
  23. import time
  24. import sys
  25. import os
  26. from datetime import datetime
  27. PIU_FARED_VERSION = "0.1.0"
  28. cur_t = int(time.time())
  29. opt = {
  30. "verbose" : 2,
  31. "state_fn" : "/home/bus/config/piufared.state",
  32. "sleepy" : 0.015625,
  33. "ratelimit_mark" : 0.0,
  34. "ratelimit_t" : 10.0,
  35. "ratelimit_n" : 40,
  36. "ratelimit_count" : 0,
  37. "dev" : "/dev/ttyPIU",
  38. "baud" : 115200,
  39. "serial_timeout" : 0.05,
  40. "MAX_BUF" : 1024,
  41. "mag_fn" : "/home/bus/log/credential.mag",
  42. "rfid_fn" : "/home/bus/log/credential.rfid",
  43. "qr_fn" : "/home/bus/log/credential.barcode",
  44. "mag_ts" : cur_t,
  45. "rfid_ts" : cur_t,
  46. "qr_ts" : cur_t
  47. }
  48. def _DT():
  49. return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  50. # We allow `reatelimit_n` message bursts.
  51. # If the number of messages exceeds `ratelimit_n`, then
  52. # we have a moratorium on messages for `ratelimit_t` seconds.
  53. # Once the time watermark is exceeded, the count and time
  54. # watermark reset.
  55. #
  56. # Return True if we should ratelimit
  57. # Return False if we don't need to ratelimit
  58. #
  59. def ratelimit(_opt):
  60. if time.time() > _opt["ratelimit_mark"]:
  61. _opt["ratelimit_count"] = 0
  62. _opt["ratelimit_mark"] = time.time() + _opt["ratelimit_t"]
  63. return False
  64. elif _opt["ratelimit_count"] < _opt["ratelimit_n"]:
  65. _opt["ratelimit_count"] += 1
  66. #_opt["ratelimit_mark"] = time.time() + _opt["ratelimit_t"]
  67. return False
  68. return True
  69. def write_state(_opt):
  70. with open(opt["state_fn"], "w") as ofp:
  71. ofp.write("mag_ts " + str(_opt["mag_ts"]) + "\nrfid_ts " + str(_opt["rfid_ts"]) + "\nqr_ts " + str(_opt["qr_ts"]) + "\n")
  72. def read_state(_opt):
  73. with open(_opt["state_fn"]) as fp:
  74. lines = fp.read().split("\n")
  75. for line in lines:
  76. tok = line.split(" ")
  77. if len(tok) < 2: continue
  78. if tok[0] == "mag_ts":
  79. _opt["mag_ts"] = int(tok[1])
  80. elif tok[0] == "rfid_ts":
  81. _opt["rfid_ts"] = int(tok[1])
  82. elif tok[0] == "qr_ts":
  83. _opt["qr_ts"] = int(tok[1])
  84. def send_data(_ser, _s):
  85. #_s = dat + "\r\n"
  86. _ser.write(_s.encode("utf-8"))
  87. # if we don't have a state file, create a new
  88. # one with the default (0) values
  89. #
  90. if not os.path.isfile(opt["state_fn"]):
  91. write_state(opt)
  92. read_state(opt)
  93. piu_serial = serial.Serial(opt["dev"], baudrate=opt["baud"], timeout=opt["serial_timeout"])
  94. #_msg = '/M:;255100010014851?\r\n'
  95. #piu_serial.write(_msg.encode('utf-8'))
  96. sys.stdout.flush()
  97. buf = []
  98. while True:
  99. eol = False
  100. read_len = -1
  101. have_data = False
  102. while (len(buf) < opt["MAX_BUF"]) and (not eol) and (read_len != 0):
  103. b = piu_serial.read()
  104. read_len = len(b)
  105. for _b in b:
  106. if _b == ord('\r') or _b == ord('\n'):
  107. eol = True
  108. have_data = True
  109. break
  110. else:
  111. buf.append(chr(_b))
  112. if len(buf) >= opt["MAX_BUF"]:
  113. have_data = True
  114. if have_data:
  115. s = "".join(buf)
  116. buf = []
  117. if len(s)==0:
  118. if not ratelimit(opt):
  119. ## DEBUG
  120. if opt["verbose"] > 0:
  121. print(_DT(), "[piufared]", "sending init msg '/?: ?=rider_ui\\r\\n")
  122. sys.stdout.flush()
  123. send_data(piu_serial, "/?: ?=rider_ui\r\n")
  124. continue
  125. elif s[0:3] == "/0:":
  126. if not ratelimit(opt):
  127. ## DEBUG
  128. if opt["verbose"] > 0:
  129. print(_DT(), "[piufared]", "sending /ok: resp to '/0:'")
  130. sys.stdout.flush()
  131. send_data(piu_serial, "/ok:\r\n")
  132. elif s[0:3] == "/1:":
  133. if not ratelimit(opt):
  134. ## DEBUG
  135. if opt["verbose"] > 0:
  136. print(_DT(), "[piufared]", "sending /ok: resp to '/1:'")
  137. sys.stdout.flush()
  138. send_data(piu_serial, "/ok:\r\n")
  139. elif s[0:3] == "/m:":
  140. if not ratelimit(opt):
  141. ## DEBUG
  142. if opt["verbose"] > 0:
  143. print(_DT(), "[piufared]", "sending '/OK:m:09 00 03 42 5A 44 56\\r\\n'")
  144. sys.stdout.flush()
  145. # init sequence the DIU expects
  146. # hard coded for now.
  147. # should echo received message?
  148. #
  149. send_data(piu_serial, "/OK:m:09 00 03 42 5A 44 56\r\n")
  150. send_data(piu_serial, "/M:^\r\n")
  151. if len(s) < 3: continue
  152. if s[0] != '/': continue
  153. if s[2] != ':': continue
  154. msg = s[3:]
  155. if s[1] == '0':
  156. os.system("/home/bus/bin/piumsg 'relay passenger_message " + msg + "'")
  157. elif s[1] == '1':
  158. os.system("/home/bus/bin/piumsg 'relay passenger_notify " + msg + "'")
  159. ###
  160. state_change = False
  161. with open(opt["mag_fn"]) as fp:
  162. lines = fp.read().split("\n")
  163. for idx in range(len(lines)-1):
  164. line = lines[idx]
  165. tok = line.split(":")
  166. if len(tok)<2: continue
  167. _ts = int(tok[0])
  168. tok[1] = re.sub('^ *', '', tok[1])
  169. if _ts <= opt["mag_ts"]: continue
  170. if opt["verbose"] > 0:
  171. print(_DT(), "[piufared]", "sending mag", " ".join(tok[1:]))
  172. send_data(piu_serial, "/M:" + " ".join(tok[1:]) + "\r\n")
  173. opt["mag_ts"] = _ts
  174. state_change = True
  175. with open(opt["rfid_fn"]) as fp:
  176. lines = fp.read().split("\n")
  177. for idx in range(len(lines)-1):
  178. line = lines[idx]
  179. tok = line.split(":")
  180. if len(tok)<2: continue
  181. _ts = int(tok[0])
  182. tok[1] = re.sub('^ *', '', tok[1])
  183. if _ts <= opt["rfid_ts"]: continue
  184. if opt["verbose"] > 0:
  185. print(_DT(), "[piufared]", "sending rfid", " ".join(tok[1:]))
  186. send_data(piu_serial, "/R:" + " ".join(tok[1:]) + "\r\n")
  187. opt["rfid_ts"] = _ts
  188. state_change = True
  189. with open(opt["qr_fn"]) as fp:
  190. lines = fp.read().split("\n")
  191. for idx in range(len(lines)-1):
  192. line = lines[idx]
  193. tok = line.split(":")
  194. if len(tok)<2: continue
  195. _ts = int(tok[0])
  196. tok[1] = re.sub('^ *', '', tok[1])
  197. if _ts <= opt["qr_ts"]: continue
  198. if opt["verbose"] > 0:
  199. print(_DT(), "[piufared]", "sending qr", " ".join(tok[1:]))
  200. send_data(piu_serial, "/Q:" + " ".join(tok[1:]) + "\r\n")
  201. opt["qr_ts"] = _ts
  202. state_change = True
  203. if state_change:
  204. if opt["verbose"] > 1:
  205. print(_DT(), "[piufared]", "state change, writing")
  206. write_state(opt)
  207. time.sleep(opt["sleepy"])