def upd_member(openid): """ If operation is "update": Update a member record designated by the provided 'openid' If operation is "add": Add a new transaction to the designated member """ member = Member_Api(openid=openid) if member.id: operations = request.json if operations['op'] == "update": # update a member's profile upd_fields = {'id': member.id} for field in [ f for f in operations['data'] if f in Member_Api.API_MAPPING_TO_DB ]: upd_fields[Member_Api.API_MAPPING_TO_DB[field]] = operations[ 'data'][field] if upd_fields: if http_view.controller.debug: print("Update", openid, "with", upd_fields) result = member.update(**upd_fields) elif operations['op'] == "add": # add a new payment record result = member.add_payment(operations['data']) else: result = member.to_json() # generates the 1999 message return result
def test_from_json_by_openid_1(self): """GET the Admin profile - openid=1""" url = "{}/member/openid/{}".format(self.base_url, "a3c56532") print("Path:", url) response = requests.get(url) self.assertEqual(200, response.status_code) admin = response.json() self.print_JSON(admin, title="test_from_json_by_openid_1:") self.assertEqual(admin['basicInfo']['nickName'], "admin") m = Member_Api() m.from_json(admin) self.assertEqual(m.id, 1) self.assertEqual(m['username'], "admin")
class Controller(object): def __init__(self, bottle_ip=None, port=8080, debug=False): self.bottle_ip, self.port = bottle_ip, port self.debug = debug # init local attributes self.db = Database() if debug: print("has_RFID:", self.db.access["has_RFID"]) print("has_camera:", self.db.access["has_camera"]) print("open_with:", self.db.access["open_with"]) print("wait_to_close:", self.db.access["wait_to_close"]) self.base_api_url = "http://{}:8080/api/v1.0".format( bottle_ip or Database.server_ip) self.gpio = Rpi_Gpio(has_PN532=self.db.access["has_RFID"]) self.member = None _OPEN_WITH = { "BLE": self.open_the_door_BLE, "RELAY": self.open_the_door_RELAY, "BOTH": self.open_the_door_BOTH, "API": self.open_the_door_API, } self.TASKS = { 1: self.wait_for_proximity, 2: self.capture_qrcode, 3: self.check_member, 4: _OPEN_WITH[self.db.access[ "open_with"]], # choose the function to call: _RELAY, _BLE, _BOTH, _API 5: self.bad_member_status, 6: self.unknown_qr_code, 7: self. wait_to_close_the_door, # self.db.access["wait_to_close"] is 0 or 1 (False/True) 98: self.panic_no_db, 99: self.panic_mode, } self.camera = Camera(self.db) if self.db.access["has_camera"] else None self.last_entries = myQ() # veifies that the database is accessible url = "{}/member/{}".format(self.base_api_url, 1) try: response = requests.get(url) print("Connected and member fetched:", response) except requests.ConnectionError as conn_err: print("Cannot start due to", conn_err) exit(98) def insert_log(self, log_type, code, msg): """ Insert a log record in the database for tracking and print a message on terminal :param log_type: OPEN / ERROR / NOT_OK :param msg: a free message """ # self.db.log(log_type, code, msg, debug=self.debug) url = "{}/log/add".format(self.base_api_url) payload = { "log_type": log_type, "code": code, "msg": msg, "debug": self.debug } try: response = requests.post(url, json=payload) except requests.ConnectionError as conn_err: self.panic_no_db() return 98 if response.status_code == 201: if self.debug: print(payload) print(response.text) else: print("Cannot create Log entry with response.status_code:", response.status_code) def stop(self): """Clean up before stopping""" self.gpio.pig.stop() if self.camera: self.camera.close() # bottle_stop() def run(self): """ Main loop to move from one state to the next :return: """ with open("zhima.pid", "wt") as fpid: print(os.getpid(), file=fpid) def stop_handler(signum, frame): """ allow: kill -10 `cat ponicwatch.pid` """ self.stop() sys.exit() signal.signal(signal.SIGUSR1, stop_handler) self.current_state = 1 # initial state: waiting for proximity detection # http_view.controller = self #thread.start_new_thread(http_view.run, (, )) # t = threading.Thread(target=http_view.run, kwargs={'host':self.bottle_ip, 'port': self.port}) # t.start() try: while self.current_state: # can be stopped by program by return a next state as 0 task = self.TASKS[self.current_state] self.current_state = task() finally: self.stop() def wait_for_proximity(self): """State 1: Use GPIO to wait for a person to present - a mobile phone to the camera by checking the proximity detector - a RFID card by attempting to read a UID""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.flash("SET", on_duration=0.25, off_duration=1) self.gpio.green2.OFF() self.gpio.red.OFF() self.gpio.relay.OFF() self.gpio.proximity.reset() # display the waiting message points, max_pts = 1, 10 next_state = 1 self.qr_codes, self.uid = [], None while next_state == 1: # sleep(0.2) if self.debug: print("Waiting for proximity", '.' * points, " " * max_pts, end="\r") points = (points + 1) % max_pts if self.gpio.check_proximity(): next_state = 2 if self.db.access[ "has_camera"] else 4 # take QR Code photo / open the door immediately break # Check if a card is available to read. if self.db.access["has_RFID"]: try: self.uid = self.gpio.pn532.read_passive_target(as_hex=True) except RuntimeError as err: print(err) return 99 if self.uid: if self.debug: print("Read RFID UID", self.uid) next_state = 3 else: sleep(0.2) # to prevent over heating the RFID card reader return next_state def capture_qrcode(self): """State 2: take photos until a QR code is detected""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.ON() self.gpio.green2.flash("SET", on_duration=0.5, off_duration=0.5) self.gpio.red.OFF() self.qr_codes = self.camera.get_QRcode( max_photos=2, debug=self.debug) if self.camera else [] if self.qr_codes is None: # webcam is not working: panic mode: all LED flashing! return 99 return 3 if self.qr_codes else 1 # qr_codes is a list --> 3, might be an empty one --> back to 1 def double_sandwich(self): """Check if we need to give 6 months membership registration""" try: m1, m2, m3, m4 = [m for t, m in self.last_entries[:4]] except ValueError: return False if m1.id == m3.id == m4.id and m1.id != m2.id and m1.is_staff: return (datetime.now() - self.last_entries[3][0]).seconds <= 20 else: return False def single_sandwich(self): """Check if we need to give 1 month membership registration""" try: m1, m2, m3 = [m for t, m in self.last_entries[:3]] except ValueError: return False if m1.id == m3.id and m1.id != m2.id and m1.is_staff: return (datetime.now() - self.last_entries[2][0]).seconds <= 15 else: return False def eat_sandwich(self, size): """Execute the posting of a payment transaction - size = 1 (1 mth) or 2 (6 mths) for single or double sandwich +31 days for single +181 days for double """ msg = "** Double Sandwich ** " if size == 2 else "* Single Sandwich * " timestamp1, member_to_upd = self.last_entries[1] timestamp0, staff_member = self.last_entries[0] membership = Member_Api.MEMBERSHIP[size - 1] # list starts at 0 amount = membership[2] until_days = membership[3] new_validity = member_to_upd['validity'] + timedelta(days=until_days) msg += "{}CNY received from {} until {:%Y-%m-%d} (staff {})".format( amount, member_to_upd['username'], new_validity, staff_member['username']) # Add Transaction by API using the 'openid' as PATCH is only implemented on that table key url = "{}/member/openid/{}".format(self.base_api_url, member_to_upd["openid"]) patch = { "op": "add", "data": { 'paidTime': datetime.now().strftime('%Y%m%d%H%M'), # '201803121929', 'payIndex': msg, 'CNYAmount': amount, 'payType': membership[0], 'until_days': until_days } } try: response = requests.patch( url, json=patch) # PATCH API call to add a transaction record except requests.ConnectionError as conn_err: return 98 if response.status_code == 200: result = response.json() print(result) if int(result['errno']) == 1000: # Perform the confirmation flashing self.gpio.green1.flash("SET", on_duration=1, off_duration=1) self.gpio.green2.flash("SET", on_duration=1, off_duration=1) self.gpio.red.flash("SET", on_duration=1, off_duration=1) for i in range(size * 2): sleep(1) # Log payment self.insert_log("PAY", 100 + size, msg) return 1 else: self.insert_log( "PAY", -100 - size, "Failed record payment: {} ({})".format( msg, result['errmsg'])) else: self.insert_log( "PAY", -100 - size, "Failed record payment: {} ({})".format( msg, response.status_code)) return 99 def check_member(self): """State 3: a QR Code is found: check it against the member database""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.ON() self.gpio.green2.ON() self.gpio.red.OFF() self.member = Member_Api() if self.qr_codes: # try finding a member from the database based on the first QR code found on the image # print("QR code:", self.qr_codes, type(self.qr_codes[0])) # self.member = Member(qrcode=self.qr_codes[0]) self.member.id = self.member.decode_qrcode(self.qr_codes[0]) if self.member.id: if self.member.qrcode_is_valid: url = "{}/member/{}".format(self.base_api_url, self.member.id) try: response = requests.get(url) except requests.ConnectionError as conn_err: print(conn_err) return 98 if response.status_code == 200: self.member.from_json(response.json()) if self.debug: print(self.member) else: self.insert_log( "ERROR", -1399, "response: {} for {}".format( response.status_code, url)) else: self.insert_log( "ERROR", -1001, "Invalid XCJ QR code: {}".format( self.member.clear_qrcode)) else: self.insert_log( "ERROR", -1000, "Non XCJ QR Code or No member found for: {}".format( self.qr_codes[0].decode("utf-8"))) elif self.uid: # try by RFID UID url = "{}/member/rfid/{}".format(self.base_api_url, self.uid) try: response = requests.get(url) except requests.ConnectionError as conn_err: print(conn_err) return 98 if response.status_code == 200: self.member.from_json(response.json()) if self.debug: print(self.member) else: self.insert_log( "ERROR", -1398, "response: {} for {}".format(response.status_code, url)) if self.member.id is None: self.insert_log( "ERROR", -1010, "Non XCJ registered RFID card: {}".format(self.uid)) # if we have a member, let's check its status to open the door if self.member.id: self.last_entries.add( (datetime.now(), self.member)) # stack for sandwich checking if self.double_sandwich(): return self.eat_sandwich(size=2) elif self.single_sandwich(): return self.eat_sandwich(size=1) elif self.uid or self.member.qrcode_is_valid: if self.member['status'].upper() in ("OK", "ACTIVE", "ENROLLED"): msg = "Welcome {} - {}".format(self.member['username'], "RFID {}".format(self.uid) if self.uid \ else "QR v{} {}".format(self.member.qrcode_version, ":".join(self.member.clear_qrcode[2:]))) self.insert_log("OPEN", self.member.id, msg) return 4 else: self.insert_log( "NOT_OK", self.member.id, "{}, please fix your status: {}".format( self.member['username'], self.member['status'])) return 5 return 6 def happy_flashing(self, duration): """Flash alternatively the green lights for 'duration' seconds""" on_off = 0 for i in range(duration * 4): self.gpio.green1.set(on_off) on_off = int(not on_off) self.gpio.green2.set(on_off) sleep(0.25) def open_the_door_API(self): """State 4: Proceed to open the door by calling the API --> open main door ; RPI does not control a door directly Calls the Master Raspi to open the door by API""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) url = "{}/open/seconds".format(self.base_api_url) payload = {'seconds': 3} try: response = requests.post(url, json=payload) except requests.ConnectionError as conn_err: return 98 if response.status_code == 200: result = response.json() if result["errno"] == 1000: # no error self.happy_flashing(3) return 7 if self.db.access["wait_to_close"] else 1 else: self.insert_log( "ERROR", -1002, "Cannot open door by API: {} {}".format( result["errno"], result["errmsg"])) else: self.insert_log( "ERROR", -1003, "Cannot open door by API: response.status_code = {}".format( response.status_code)) return 6 def open_the_door_RELAY(self): """State 4: Proceed to open the door Open the door using the electric relay """ if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.relay.ON() self.happy_flashing(3) return 7 if self.db.access["wait_to_close"] else 1 def open_the_door_BLE(self): """State 4: Proceed to open the door Open the door using Bluetooth - all BLE parameters are defaulted for the TOKYDOOR BLE @ XCJ """ if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) tokydoor = TokyDoor(database=self.db) try: tokydoor.open() except ValueError as err: self.insert_log("ERROR", -1001, "Cannot connect to TOKYDOOR: {}".format(err)) return 99 # cannot reach the BLE!!! Panic Mode!! self.happy_flashing(3) return 7 if self.db.access["wait_to_close"] else 1 def open_the_door_BOTH(self): """State 4: Proceed to open the door Open the door using both Bluetooth - all BLE parameters are defaulted for the TOKYDOOR BLE @ XCJ and the electric relay """ if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.relay.ON() tokydoor = TokyDoor(database=self.db) try: tokydoor.open() except ValueError as err: self.insert_log("ERROR", -1001, "Cannot connect to TOKYDOOR: {}".format(err)) return 99 # cannot reach the BLE!!! Panic Mode!! self.happy_flashing(3) return 7 if self.db.access["wait_to_close"] else 1 def wait_to_close_the_door(self): """State 7: Wait for the door to be closed Verifies that the door has been closed physically before returning to State 1 where the relay set OFF""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) while self.gpio.door_check.is_open: self.happy_flashing(1) return 1 def bad_member_status(self): """State 5: Member "bad status", email the member to warn about the status""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.OFF() self.gpio.green2.ON() self.gpio.red.ON() if self.member["email"]: print("Email to", self.member['username']) send_email("Sorry, XCJ doorman cannot open the door for you", from_=self.db.mailbox["username"], to_=(self.member["email"], ), message_HTML=""" <P>Your status in the XCJ database is set to: {}</P> <p>You membership expiration date is {}</p> <p></p> """.format(self.member["status"], self.member.validity.strftime('%Y-%m-%d')), images=[r"images/emoji-not-happy.jpg"], server=self.db.mailbox["server"], port=self.db.mailbox["port"], login=self.db.mailbox["username"], passwd=self.db.mailbox["password"]) sleep(1.5 ) # let's say it takes 1.5 seconds to send the email already else: sleep(3) return 1 def unknown_qr_code(self): """State 6: A QR code or RFID UID was read but does not match our known pattern OR no member found""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.ON() self.gpio.green2.OFF() self.gpio.red.ON() sleep(3) return 1 def panic_no_db(self): """State 98: no database acces, flash the lights for 3 seconds""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.flash("SET", on_duration=0.3, off_duration=0.3) self.gpio.green2.flash("SET", on_duration=0.3, off_duration=0.3) self.gpio.red.ON() sleep(3) return 1 def panic_mode(self): """State 99: major error, flash the lights for 3 seconds""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.flash("SET", on_duration=0.3, off_duration=0.3) self.gpio.green2.flash("SET", on_duration=0.3, off_duration=0.3) self.gpio.red.flash("SET", on_duration=0.3, off_duration=0.3) sleep(3) return 1
def check_member(self): """State 3: a QR Code is found: check it against the member database""" if self.debug: print("Entering state", self.current_state, self.TASKS[self.current_state].__name__) self.gpio.green1.ON() self.gpio.green2.ON() self.gpio.red.OFF() self.member = Member_Api() if self.qr_codes: # try finding a member from the database based on the first QR code found on the image # print("QR code:", self.qr_codes, type(self.qr_codes[0])) # self.member = Member(qrcode=self.qr_codes[0]) self.member.id = self.member.decode_qrcode(self.qr_codes[0]) if self.member.id: if self.member.qrcode_is_valid: url = "{}/member/{}".format(self.base_api_url, self.member.id) try: response = requests.get(url) except requests.ConnectionError as conn_err: print(conn_err) return 98 if response.status_code == 200: self.member.from_json(response.json()) if self.debug: print(self.member) else: self.insert_log( "ERROR", -1399, "response: {} for {}".format( response.status_code, url)) else: self.insert_log( "ERROR", -1001, "Invalid XCJ QR code: {}".format( self.member.clear_qrcode)) else: self.insert_log( "ERROR", -1000, "Non XCJ QR Code or No member found for: {}".format( self.qr_codes[0].decode("utf-8"))) elif self.uid: # try by RFID UID url = "{}/member/rfid/{}".format(self.base_api_url, self.uid) try: response = requests.get(url) except requests.ConnectionError as conn_err: print(conn_err) return 98 if response.status_code == 200: self.member.from_json(response.json()) if self.debug: print(self.member) else: self.insert_log( "ERROR", -1398, "response: {} for {}".format(response.status_code, url)) if self.member.id is None: self.insert_log( "ERROR", -1010, "Non XCJ registered RFID card: {}".format(self.uid)) # if we have a member, let's check its status to open the door if self.member.id: self.last_entries.add( (datetime.now(), self.member)) # stack for sandwich checking if self.double_sandwich(): return self.eat_sandwich(size=2) elif self.single_sandwich(): return self.eat_sandwich(size=1) elif self.uid or self.member.qrcode_is_valid: if self.member['status'].upper() in ("OK", "ACTIVE", "ENROLLED"): msg = "Welcome {} - {}".format(self.member['username'], "RFID {}".format(self.uid) if self.uid \ else "QR v{} {}".format(self.member.qrcode_version, ":".join(self.member.clear_qrcode[2:]))) self.insert_log("OPEN", self.member.id, msg) return 4 else: self.insert_log( "NOT_OK", self.member.id, "{}, please fix your status: {}".format( self.member['username'], self.member['status'])) return 5 return 6
def add_member(): member = Member_Api() # empty new member result = member.create_from_json(request.json) return result
def get_member_by_rfid(rfid): """Return a Member JSON object based on a member db record and its last transaction""" member = Member_Api(rfid=rfid) return member.to_json()