class CollectionModule(ModuleProcess): # You can keep these parameters the same, all modules receive the same params # self - reference to self # baseConfig - configuration settings defined in /simplesensor/config/base.conf # (https://github.com/AdobeAtAdobe/SimpleSensor/blob/master/config/base.conf) # pInBoundQueue - messages from handler to this module # pOutBoundQueue - messages from this module to other modules # loggingQueue - logging messages for threadsafe logger def __init__(self, baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue): """ Initialize new CollectionModule instance. """ super(CollectionModule, self).__init__(baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue) # Most collection modules will follow a similar pattern... # 1. Set up some variables on the self object # Queues self.outQueue = pOutBoundQueue self.inQueue= pInBoundQueue self.loggingQueue = loggingQueue self.threadProcessQueue = None self.alive = False self.context = None self.reader = None # 2. Load the module's configuration file # Configs self.moduleConfig = configLoader.load(self.loggingQueue, __name__) self.config = baseConfig # 3. Set some constants to the self object from config parameters (if you want) self._id = self.moduleConfig['CollectionPointId'] self._type = self.moduleConfig['CollectionPointType'] self._port = self.moduleConfig['ReaderPortNumber'] # 4. Create a threadsafe logger object self.logger = ThreadsafeLogger(loggingQueue, __name__) def run(self): """ Main process method, run when the thread's start() function is called. Starts monitoring inbound messages to this module, and collection logic goes here. For example, you could put a loop with a small delay to keep polling the sensor, etc. When something is detected that's relevant, put a message on the outbound queue. """ # Monitor inbound queue on own thread self.listen() self.alive = True while self.context == None: self.establish_context() while self.alive: while self.reader == None: self.reader = self.get_reader() if self.reader is None: self.logger.info('Waiting for 5 seconds before ' + 'trying to find readers again. Is it plugged in?') time.sleep(5) # connect to card card = self.get_card() if card is None: continue # get block #10 and 11 of card, # contains the attendee ID msg = [0xFF, 0xB0, 0x00, bytes([10])[0], 0x04] chunk_one = self.send_transmission(card, msg) if chunk_one is None: self.reader = None continue msg = [0xFF, 0xB0, 0x00, bytes([11])[0], 0x04] chunk_two = self.send_transmission(card, msg) if chunk_two is None: self.reader = None continue # the id is in B1-3 of block 10 # and B0-2 of block 11 attendee_id_bytes = bytearray(chunk_one[1:4]+chunk_two[0:3]) attendee_id = attendee_id_bytes.decode('UTF-8') xdata = { 'attendee_id': attendee_id } msg = self.build_message(topic='scan_in', extendedData=xdata) self.logger.info('Sending message: {}'.format(msg)) self.put_message(msg) self.reader = None # sleep for a bit to avoid double scanning time.sleep(5) def establish_context(self): hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER) if hresult != SCARD_S_SUCCESS: self.logger.error( 'Unable to establish context: {}'.format( SCardGetErrorMessage(hresult))) return self.context = hcontext def get_reader(self): hresult, readers = SCardListReaders(self.context, []) if hresult != SCARD_S_SUCCESS: self.logger.error( 'Failed to list readers: {}'.format( SCardGetErrorMessage(hresult))) return if len(readers)<1 or len(readers)-1<self._port: self.logger.error( 'Not enough readers attached. {} needed, {} attached'.format( (self._port+1), (len(readers)))) return else: return readers[self._port] def get_card(self, mode=None, protocol=None): hresult, hcard, dwActiveProtocol = SCardConnect( self.context, self.reader, mode or SCARD_SHARE_SHARED, protocol or (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) if hresult != SCARD_S_SUCCESS: return else: return hcard def send_transmission(self, card, msg, protocol=None): hresult, response = SCardTransmit( card, protocol or SCARD_PCI_T1, msg) if hresult != SCARD_S_SUCCESS: self.logger.error( 'Failed to send transmission: {}'.format( SCardGetErrorMessage(hresult))) return else: return response[:-2] def listen(self): """ Start thread to monitor inbound messages, declare module alive. """ self.threadProcessQueue = Thread(target=self.process_queue) self.threadProcessQueue.setDaemon(True) self.threadProcessQueue.start() def build_message(self, topic, extendedData={}, recipients=['communication_modules']): """ Create a Message instance. topic (required): message type sender_id (required): id property of original sender sender_type (optional): type of sender, ie. collection point type, module name, hostname, etc extended_data (optional): payload to deliver to recipient(s) recipients (optional): module name, which module(s) the message will be delivered to, ie. `websocket_server`. use an array of strings to define multiple modules to send to. use 'all' to send to all available modules. use 'local_only' to send only to modules with `low_cost` prop set to True. [DEFAULT] use 'communication_modules' to send only to communication modules. use 'collection_modules' to send only to collection modules. """ msg = Message( topic=topic, sender_id=self._id, sender_type=self._type, extended_data=extendedData, recipients=recipients, timestamp=datetime.datetime.utcnow()) return msg def put_message(self, msg): """ Put message onto outgoing queue. """ self.outQueue.put(msg) def process_queue(self): """ Process inbound messages on separate thread. When a message is encountered, trigger an event to handle it. Sleep for some small amount of time to avoid overloading. Also receives a SHUTDOWN message from the main process when the user presses the esc key. """ self.logger.info("Starting to watch collection point inbound message queue") while self.alive: if (self.inQueue.empty() == False): self.logger.info("Queue size is %s" % self.inQueue.qsize()) try: message = self.inQueue.get(block=False,timeout=1) if message is not None: self.handle_message(message) except Exception as e: self.logger.error("Error, unable to read queue: %s " %e) self.shutdown() self.logger.info("Queue size is %s after" % self.inQueue.qsize()) else: time.sleep(.25) def handle_message(self, message): """ Handle messages from other modules to this one. Switch on the message topic, do something with the data fields. """ if message.topic.upper()=='SHUTDOWN' and message.sender_id=='main': self.shutdown() def shutdown(self): """ Shutdown the collection module. Set alive flag to false so it stops looping. Wait for things to die, then exit. """ self.alive = False print("Shutting down nfc_bcard_reader") time.sleep(1) self.exit = True
class Tracker(): def __init__(self, bbox, frame, kind, moduleConfig, loggingQueue): """ Create and initialize a new Tracker. Set up constants and parameters. """ if kind in ["KCF", "MIL", "MEDIANFLOW", "GOTURN", "TLD", "BOOSTING"]: self.tracker = cv2.Tracker_create(kind) self.tracker.init(frame, (bbox['x'], bbox['y'], bbox['w'], bbox['h'])) self.created = time() self.bbox = (bbox['x'], bbox['y'], bbox['w'], bbox['h']) self.velocity = (0, 0) self.updateTime = self.created self.config = moduleConfig # Constants self._useVelocity = self.config['UseVelocity'] self._horizontalVelocityBuffer = self.config['HorizontalVelocityBuffer'] self._verticalVelocityBuffer = self.config['VerticalVelocityBuffer'] # Setup logging queue self.logger = ThreadsafeLogger(loggingQueue, __name__) else: self.logger.error("Type %s not supported by mTracker" % kind) def getCreated(self): """ Get created time """ return self.created def right(self): """ Get right bound of tracker """ return self.bbox[0] + self.bbox[2] def top(self): """ Get top bound of tracker """ return self.bbox[1] + self.bbox[3] def bottom(self): """ Get bottom bound of tracker """ return self.bbox[1] def left(self): """ Get left bound of tracker """ return self.bbox[0] def area(self): """ Get area of tracker bounding box """ return abs(self.right() - self.left())*abs(self.top() - self.bottom()) def update(self, frame): """ Update tracker. If velocity hack is being used, calculate the new velocity of the midpoint. """ ok, bbox = self.tracker.update(frame) if self._useVelocity: # Set velocity (pixels/sec) deltaT = time() - self.updateTime centreNow = ((bbox[0]+bbox[2]/2), (bbox[1]+bbox[3]/2)) centreLast = ((self.bbox[0]+self.bbox[2]/2), (self.bbox[1]+self.bbox[3]/2)) Vx = (centreNow[0] - centreLast[0])/deltaT Vy = (centreNow[1] - centreLast[1])/deltaT self.velocity = (Vx, Vy) self.logger.debug('New velocity: %s' % str(self.velocity[0])+', '+str(self.velocity[1])) self.updateTime = time() self.bbox = bbox return ok, bbox def getProjectedLocation(self, time): """ Get the estimated location of the bounding box, based on previous velocity. """ deltaT = max((time - self.updateTime), 1) centreNow = ((self.bbox[0]+self.bbox[2]/2), (self.bbox[1]+self.bbox[3]/2)) projectedX = centreNow[0]+(self.velocity[0]*deltaT) projectedY = centreNow[1]+(self.velocity[1]*deltaT) return (projectedX, projectedY) def getVelocityBuffer(self): ''' Another hack to improve low frame rate tracking. "Spread" out the bounding box based on velocity. ''' return (abs(self.velocity[0])*self._horizontalVelocityBuffer, abs(self.velocity[1])*self._verticalVelocityBuffer)
class BtleThreadCollectionPoint(object): def __init__(self, clientEventHandler, btleConfig, loggingQueue, debugMode=False): # Logger self.loggingQueue = loggingQueue self.logger = ThreadsafeLogger(loggingQueue, __name__) self.btleConfig = btleConfig self.clientEventHandler = clientEventHandler self.debug = debugMode # define basic BGAPI parser self.bgapi_rx_buffer = [] self.bgapi_rx_expected_length = 0 def start(self): packet_mode = False # create BGLib object self.ble = BGLib() self.ble.packet_mode = packet_mode self.ble.debug = self.debug # add handler for BGAPI timeout condition (hopefully won't happen) self.ble.on_timeout += self.my_timeout # on busy hander self.ble.on_busy = self.on_busy # add handler for the gap_scan_response event self.ble.ble_evt_gap_scan_response += self.clientEventHandler # create serial port object and flush buffers self.logger.info( "Establishing serial connection to BLED112 on com port %s at baud rate %s" % (self.btleConfig['BtleDeviceId'], self.btleConfig['BtleDeviceBaudRate'])) self.serial = Serial(port=self.btleConfig['BtleDeviceId'], baudrate=self.btleConfig['BtleDeviceBaudRate'], timeout=1) self.serial.flushInput() self.serial.flushOutput() # disconnect if we are connected already self.ble.send_command(self.serial, self.ble.ble_cmd_connection_disconnect(0)) self.ble.check_activity(self.serial, 1) # stop advertising if we are advertising already self.ble.send_command(self.serial, self.ble.ble_cmd_gap_set_mode(0, 0)) self.ble.check_activity(self.serial, 1) # stop scanning if we are scanning already self.ble.send_command(self.serial, self.ble.ble_cmd_gap_end_procedure()) self.ble.check_activity(self.serial, 1) # set the TX # range 0 to 15 (real TX power from -23 to +3dBm) #self.ble.send_command(self.serial, self.ble.ble_cmd_hardware_set_txpower(self.btleConfig['btleDeviceTxPower'])) #self.ble.check_activity(self.serial,1) #ble_cmd_connection_update connection: 0 (0x00) interval_min: 30 (0x001e) interval_max: 46 (0x002e) latency: 0 (0x0000) timeout: 100 (0x0064) #interval_min 6-3200 #interval_man 6-3200 #latency 0-500 #timeout 10-3200 self.ble.send_command( self.serial, self.ble.ble_cmd_connection_update(0x00, 0x001e, 0x002e, 0x0000, 0x0064)) self.ble.check_activity(self.serial, 1) # set scan parameters #scan_interval 0x4 - 0x4000 #Scan interval defines the interval when scanning is re-started in units of 625us # Range: 0x4 - 0x4000 # Default: 0x4B (75ms) # After every scan interval the scanner will change the frequency it operates at # at it will cycle through all the three advertisements channels in a round robin # fashion. According to the Bluetooth specification all three channels must be # used by a scanner. # #scan_window 0x4 - 0x4000 # Scan Window defines how long time the scanner will listen on a certain # frequency and try to pick up advertisement packets. Scan window is defined # as units of 625us # Range: 0x4 - 0x4000 # Default: 0x32 (50 ms) # Scan windows must be equal or smaller than scan interval # If scan window is equal to the scan interval value, then the Bluetooth module # will be scanning at a 100% duty cycle. # If scan window is half of the scan interval value, then the Bluetooth module # will be scanning at a 50% duty cycle. # #active 1=active 0=passive # 1: Active scanning is used. When an advertisement packet is received the # Bluetooth stack will send a scan request packet to the advertiser to try and # read the scan response data. # 0: Passive scanning is used. No scan request is made. #self.ble.send_command(self.serial, self.ble.ble_cmd_gap_set_scan_parameters(0x4B,0x32,1)) self.ble.send_command( self.serial, self.ble.ble_cmd_gap_set_scan_parameters(0xC8, 0xC8, 0)) self.ble.check_activity(self.serial, 1) # start scanning now self.ble.send_command(self.serial, self.ble.ble_cmd_gap_discover(1)) self.ble.check_activity(self.serial, 1) # handler to notify of an API parser timeout condition def my_timeout(self, sender, args): self.logger.error( "BGAPI timed out. Make sure the BLE device is in a known/idle state." ) # might want to try the following lines to reset, though it probably # wouldn't work at this point if it's already timed out: self.ble.send_command(self.serial, self.ble.ble_cmd_system_reset(0)) self.ble.check_activity(self.serial, 1) self.ble.send_command(self.serial, self.ble.ble_cmd_gap_discover(1)) self.ble.check_activity(self.serial, 1) def on_busy(self, sender, args): self.logger.warn("BGAPI device is busy.") def scan(self): # check for all incoming data (no timeout, non-blocking) self.ble.check_activity(self.serial)
class WebsocketClientModule(ModuleProcess): def __init__(self, baseConfig, pInBoundEventQueue, pOutBoundEventQueue, loggingQueue): super(WebsocketClientModule, self).__init__() self.alive = True self.config = baseConfig self.inQueue = pInBoundEventQueue # inQueue are messages from the main process to websocket clients self.outQueue = pOutBoundEventQueue # outQueue are messages from clients to main process self.websocketClient = None self.loggingQueue = loggingQueue self.threadProcessQueue = None # Configs self.moduleConfig = configLoader.load(self.loggingQueue, __name__) # Constants self._port = self.moduleConfig['WebsocketPort'] self._host = self.moduleConfig['WebsocketHost'] # logging setup self.logger = ThreadsafeLogger(loggingQueue, __name__) def run(self): """ Main thread entry point. Sets up websocket server and event callbacks. Starts thread to monitor inbound message queue. """ self.logger.info("Starting %s" % __name__) self.connect() def listen(self): self.threadProcessQueue = Thread(target=self.processQueue) self.threadProcessQueue.setDaemon(True) self.threadProcessQueue.start() def connect(self): #websocket.enableTrace(True) ws = websocket.WebSocketApp("ws://%s:%s"%(self._host, self._port), on_message = self.onMessage, on_error = self.onError, on_close = self.onClose) ws.on_open = self.onOpen ws.run_forever() def onError(self, ws, message): self.logger.error("Error callback fired, message: %s"%message) def onClose(self, ws): if self.alive: self.logger.warn("Closed") self.alive = False # TODO: reconnect timer else: self.logger.info("Closed") def onMessage(self, ws, message): self.logger.info("Message from websocket server: %s"%message) # Could put message on the out queue here to handle incoming coms def onOpen(self, ws): self.alive = True self.websocketClient = ws self.listen() def shutdown(self): """ Handle shutdown message. Close and shutdown websocket server. Join queue processing thread. """ self.logger.info("Shutting down %s"%__name__) try: self.logger.info("Closing websocket") self.websocketClient.close() except Exception as e: self.logger.error("Websocket close error : %s " %e) self.alive = False self.threadProcessQueue.join() time.sleep(1) self.exit = True def sendOutMessage(self, message): """ Send message to server """ self.websocketClient.send(json.dumps(message.__dict__)) def processQueue(self): """ Monitor queue of messages from main process to this thread. """ while self.alive: if (self.inQueue.empty() == False): try: message = self.inQueue.get(block=False,timeout=1) if message is not None: if (message.topic.upper()=="SHUTDOWN" and message.sender_id.lower()=="main"): self.logger.debug("SHUTDOWN handled") self.shutdown() else: self.sendOutMessage(message) except Exception as e: self.logger.error("Websocket unable to read queue : %s " %e) else: time.sleep(.25)
class MultiTracker(object): def __init__(self, kind="KCF", moduleConfig=None, loggingQueue=None): """ Create an initialize new MultiTracker. Set up constants and parameters. """ self.config = moduleConfig self.trackers = [] # List of trackers self.kind = kind self.focus = None self.loggingQueue = loggingQueue # Constants self._useVelocity = self.config['UseVelocity'] self._closestThreshold = self.config["ClosestThreshold"] self._primaryTarget = self.config['PrimaryTarget'] # Setup logging queue self.logger = ThreadsafeLogger(loggingQueue, __name__) def add(self, bbox, frame, kind="KCF"): """ Add new tracker with default type KCF. """ aTracker = Tracker(bbox, frame, kind, self.config, self.loggingQueue) self.trackers.append(aTracker) def removeAt(self, i): """ Remove Tracker at index i. """ self.trackers.pop(i) def remove(self, aTracker): """ Remove tracker provided as parameter. """ self.trackers.remove(aTracker) def update(self, frame): """ Loop through each tracker updating bounding box, keep track of failures. """ bboxes = [] ind = 0 failed = [] for aTracker in self.trackers: ok, bbox = aTracker.update(frame) if not ok: failed.append(ind) else: bboxes.append(bbox) ind += 1 if len(failed) == 0: return True, bboxes, None else: self.logger.error('Failed to update all trackers') return False, bboxes, failed def clear(self): """ Remove all trackers. """ self.trackers.clear() self.focus = None def bboxContainsPt(self, bbox, pt, vBuffer): """ Check if bbox contains pt. Optionally provide velocity buffer to spread containing space. """ if ((bbox['x'] - vBuffer[0] <= pt[0] <= (bbox['x'] + bbox['w'] + vBuffer[0])) and (bbox['y'] - vBuffer[1] <= pt[1] <= (bbox['y'] + bbox['h'] + vBuffer[1]))): return True else: return False def projectedLocationMatches(self, tracker, bbox): """ Check if the velocity of the tracker could put it in the same spot as the bbox. """ if tracker.velocity: return self.bboxContainsPt(bbox, tracker.getProjectedLocation(time()), tracker.getVelocityBuffer()) else: return False def intersects(self, tracker, bbox): """ Check if the bbox and the trackers bounds intersect. """ if (tracker.right() < bbox['x'] or bbox['x'] + bbox['w'] < tracker.left() or tracker.top() < bbox['y'] or bbox['y'] + bbox['h'] < tracker.bottom()): return False # intersection is empty else: return True # intersection is not empty def contains(self, bbox): """ Check if the MultiTracker already has a tracker for the object detected. Uses intersections and projected locations to determine if the tracker overlaps others. This means objects that overlap when first detected will not _both_ be added to the MultiTracker. """ for aTracker in self.trackers: if self._useVelocity: if self.intersects(aTracker, bbox) and self.projectedLocationMatches( aTracker, bbox): return True elif self.intersects(aTracker, bbox): return True return False def length(self): """ Get number of Trackers in the MultiTracker. """ return len(self.trackers) def getFocus(self): """ Get focal object based on primaryTarget configuration. Currently only closest is supported - checks whether there is a tracker that is larger than the previous closest tracker by the configured threshold. """ if self._primaryTarget == "closest": focusChanged = False if self.focus: # area = self.focus.area() area = self.focus.area() else: area = None for aTracker in self.trackers: # If there's no focus or aTracker is larger than focus, and they aren't the same tracker if not self.focus or ( aTracker.area() > area * (1 + (self._closestThreshold / 100)) and self.focus.getCreated() != aTracker.getCreated()): focusChanged = True self.focus = aTracker area = aTracker.area() if focusChanged: return self.focus else: return None elif self._primaryTarget == "closest_engaged": #TODO self.logger.error('Primary Target %s is not implemented.' % self._primaryTarget) return None else: self.logger.error('Primary Target %s is not implemented.' % self._primaryTarget) return None def checkFocus(self): """ Check if focal Tracker has changed by updating the focus. """ focus = self.getFocus() if focus: return True, focus.bbox else: return False, None
class BtleCollectionPoint(Thread): def __init__(self, baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue): """ Initialize new CamCollectionPoint instance. Setup queues, variables, configs, constants and loggers. """ super().__init__() # super().__init__(baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue) self.loggingQueue = loggingQueue self.logger = ThreadsafeLogger(loggingQueue, __name__) # Queues self.outQueue = pOutBoundQueue # Messages from this thread to the main process self.inQueue = pInBoundQueue # Configs self.moduleConfig = configLoader.load(self.loggingQueue, __name__) self.config = baseConfig # Variables and objects self.alive = True self.callbacks = {_ON_SCAN: self.handleBtleClientEvent} self.clientRegistry = ClientRegistry(self.moduleConfig, self.loggingQueue) self.eventManager = EventManager(self.moduleConfig, pOutBoundQueue, self.clientRegistry, self.loggingQueue) # Threads self.btleThread = None self.repeatTimerSweepClients = None self.lastUpdate = datetime.now() # Constants self._cleanupInterval = self.moduleConfig[ 'AbandonedClientCleanupInterval'] def run(self): """ Main thread entrypoint. Sets up and starts the DeviceThread. Loops repeatedly reading incoming messages. """ # Pause for a bit to let things bootup on host machine self.logger.info("Pausing execution 15 seconds" + " waiting for other system services to start") time.sleep(15) self.logger.info("Done with our nap. " + "Time to start looking for clients") # Start device thread, handles IO self.deviceThread = BlueGigaDeviceThread(self.callbacks, self.moduleConfig, self.loggingQueue) self.deviceThread.start() # Setup repeat task to run the sweep every X interval self.repeatTimerSweepClients = RepeatedTimer( (self._cleanupInterval / 1000), self.clientRegistry.sweepOldClients) self.logger.info( "Starting to watch collection point inbound message queue") while self.alive: if not self.inQueue.empty(): self.logger.info("Queue size is %s" % self.inQueue.qsize()) try: message = self.inQueue.get(block=False, timeout=1) if message is not None: if (message.topic == "SHUTDOWN" and message.sender_id == 'main'): self.logger.info("SHUTDOWN command handled on %s" % __name__) self.shutdown() else: self.handleMessage(message) except Exception as e: self.logger.error("Unable to read queue, error: %s " % e) self.shutdown() self.logger.info("Queue size is %s after" % self.inQueue.qsize()) else: time.sleep(.45) def handleBtleClientEvent(self, detectedClient): self.eventManager.registerDetectedClient(detectedClient) def handleMessage(self, msg): # Handle incoming messages, eg. from other collection points pass def killProcess(self, proc, timeout=1): """ Kill a process, given a timeout to join. """ self.logger.info('Joining process: %s' % proc) proc.join() p_sec = 0 for second in range(timeout): if proc.is_alive(): time.sleep(1) p_sec += 1 if p_sec >= timeout: self.logger.info('Terminating process: %s' % proc) proc.terminate() def shutdown(self): self.logger.info("Shutting down") self.repeatTimerSweepClients.stop() self.eventManager.stop() self.deviceThread.stop() # self.killProcess(self.deviceThread) self.alive = False time.sleep(1) self.exit = True
class BtleCollectionPoint(ModuleProcess): def __init__(self, baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue): """ Initialize new CamCollectionPoint instance. Setup queues, variables, configs, constants and loggers. """ super(BtleCollectionPoint, self).__init__() self.loggingQueue = loggingQueue self.logger = ThreadsafeLogger(loggingQueue, __name__) # Queues self.outQueue = pOutBoundQueue # Messages from this thread to the main process self.inQueue = pInBoundQueue self.queueBLE = mp.Queue() # Configs self.moduleConfig = configLoader.load(self.loggingQueue) self.config = baseConfig # Variables and objects self.registeredClientRegistry = RegisteredClientRegistry( self.moduleConfig, self.loggingQueue) self.eventManager = EventManager(self.moduleConfig, pOutBoundQueue, self.registeredClientRegistry, self.loggingQueue) self.alive = True self.btleThread = None self.BLEThread = None self.repeatTimerSweepClients = None # Constants self._cleanupInterval = self.moduleConfig[ 'AbandonedClientCleanupInterval'] def run(self): ###Pausing Startup to wait for things to start after a system restart self.logger.info( "Pausing execution 15 seconds waiting for other system services to start" ) time.sleep(15) self.logger.info( "Done with our nap. Time to start looking for clients") self.btleThread = BlueGigaBtleCollectionPointThread( self.queueBLE, self.moduleConfig, self.loggingQueue) self.BLEThread = Thread(target=self.btleThread.bleDetect, args=(__name__, 10)) self.BLEThread.daemon = True self.BLEThread.start() # Setup repeat task to run the sweep every X interval self.repeatTimerSweepClients = RepeatedTimer( (self._cleanupInterval / 1000), self.registeredClientRegistry.sweepOldClients) # Process queue from main thread for shutdown messages self.threadProcessQueue = Thread(target=self.processQueue) self.threadProcessQueue.setDaemon(True) self.threadProcessQueue.start() #read the queue while self.alive: if not self.queueBLE.empty(): result = self.queueBLE.get(block=False, timeout=1) self.__handleBtleClientEvents(result) def processQueue(self): self.logger.info( "Starting to watch collection point inbound message queue") while self.alive: if not self.inQueue.empty(): self.logger.info("Queue size is %s" % self.inQueue.qsize()) try: message = self.inQueue.get(block=False, timeout=1) if message is not None: if (message.topic == "SHUTDOWN" and message.sender_id == 'main'): self.logger.info("SHUTDOWN command handled on %s" % __name__) self.shutdown() else: self.handleMessage(message) except Exception as e: self.logger.error("Unable to read queue, error: %s " % e) self.shutdown() self.logger.info("Queue size is %s after" % self.inQueue.qsize()) else: time.sleep(.25) def __handleBtleClientEvents(self, detectedClients): for client in detectedClients: self.logger.debug("--- Found client ---") self.logger.debug(vars(client)) self.logger.debug("--- Found client end ---") self.eventManager.registerDetectedClient(client) def handleMessage(self, msg): # Handle incoming messages, eg. from other collection points pass def shutdown(self): self.logger.info("Shutting down") self.repeatTimerSweepClients.stop() self.btleThread.stop() self.alive = False time.sleep(1) self.exit = True
class DeviceThread(Thread): """ Controller thread, manage the instance of BluegigaDevice. """ def __init__(self, callbacks, btleConfig, loggingQueue, debugMode=False): super().__init__() # Logger self.loggingQueue = loggingQueue self.logger = ThreadsafeLogger(loggingQueue, __name__) self.alive = True self.callbacks = self.sanitizeCallbacks(callbacks) self.btleConfig = btleConfig # consts self._majorMin = self.btleConfig['BtleAdvertisingMajorMin'] self._majorMax = self.btleConfig['BtleAdvertisingMajorMax'] self._minorMin = self.btleConfig['BtleAdvertisingMinorMin'] self._minorMax = self.btleConfig['BtleAdvertisingMinorMax'] self._uuidFocusList = self.btleConfig['BtleUuidFocusList'] if ('any' in self._uuidFocusList or 'all' in self._uuidFocusList or len(self._uuidFocusList)==0): self.filterOnUuid = False else: self.filterOnUuid = True # self.queue = queue self.device = BluegigaDevice( self.scanCallback, self.btleConfig, self.loggingQueue) def run(self): """ Main thread entry point. Repeatedly call scan() method on device controller BluegigaDevice. Send results or failures back to main thread via callbacks. """ try: self.device.start() except Exception as e: self.logger.error("Unable to connect to BTLE device: %s"%e) self.sendFailureNotice("Unable to connect to BTLE device") self.stop() while self.alive: # try: self.device.scan() # except Exception as e: # self.logger.error("Unable to scan BTLE device: %s"%e) # self.sendFailureNotice("Unable to connect to BTLE device to perform a scan") # self.stop() # don't burden the CPU time.sleep(0.01) def scanCallback(self,sender,args): """ Callback for the scan event on the device controller. Prints the event in a formatted way for tuning purposes. """ #check to make sure there is enough data to be a beacon if len(args["data"]) > 15: try: majorNumber = args["data"][26] | (args["data"][25] << 8) # self.logger.debug("majorNumber=%i"%majorNumber) except: majorNumber = 0 try: minorNumber = args["data"][28] | (args["data"][27] << 8) # self.logger.debug("minorNumber=%i"%minorNumber) except: minorNumber = 0 try: udid = "%s" % ''.join(['%02X' % b for b in args["data"][9:25]]) except: pass if (self.filterOnUuid and udid not in self._uuidFocusList): return if (not (self._majorMin <= majorNumber <= self._majorMax) or not (self._minorMin <= minorNumber <= self._minorMax)): return rssi = args["rssi"] beaconMac = "%s" % ''.join(['%02X' % b for b in args["sender"][::-1]]) if len(args["data"]) > 29: rawTxPower = args["data"][29] else: rawTxPower = 0 if rawTxPower <= 127: txPower = rawTxPower else: txPower = rawTxPower - 256 if self.btleConfig['BtleTestMode']: self.logger.debug("=============================== eventScanResponse START ===============================") #self.logger.debug("self.btleConfig['BtleAdvertisingMinor'] == %i and self.btleConfig['BtleAdvertisingMinor'] == %i "%(majorNumber,minorNumber)) #self.logger.debug("yep, we care about this major and minor so lets create a detected client and pass it to the event manager") self.logger.debug("Major=%s"%majorNumber) self.logger.debug("Minor=%s"%minorNumber) self.logger.debug("UDID=%s"%udid) self.logger.debug("rssi=%s"%rssi) self.logger.debug("beaconMac=%s"%beaconMac) self.logger.debug("txPower=%i"%txPower) self.logger.debug("rawTxPower=%i"%rawTxPower) self.logger.debug("================================= eventScanResponse END =================================") #package it up for sending to the queue detectionData = DetectionData( 'btle', udid=udid, beaconMac=beaconMac, majorNumber=majorNumber, minorNumber=minorNumber, tx=txPower, rssi=rssi) #put it on the queue for the event manager to pick up self.callbacks[_ON_SCAN](detectionData) def sanitizeCallbacks(self, cbs): """ Make sure required callbacks are included and callable. Return only the required callbacks. """ assert(callable(cbs[_ON_SCAN])) if len(cbs) > 1: return [cbs[_ON_SCAN]] return cbs def stop(self): self.alive = False def sendFailureNotice(self, msg): if len(self.btleConfig['SlackChannelWebhookUrl']) > 10: myMsg = ("Help, I've fallen and can't get up! "+ "\n %s. \nSent from %s"%(msg,platform.node())) payload = {'text': myMsg} r = requests.post( self.btleConfig['SlackChannelWebhookUrl'], data = json.dumps(payload))
class WebsocketServerModule(ModuleProcess): def __init__(self, baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue): # super(WebsocketServerModule, self).__init__() ModuleProcess.__init__(self, baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue) self.alive = False self.config = baseConfig self.inQueue = pInBoundQueue # inQueue are messages from the main process to websocket clients self.outQueue = pOutBoundQueue # outQueue are messages from clients to main process self.websocketServer = None self.loggingQueue = loggingQueue self.threadProcessQueue = None # Configs self.moduleConfig = configLoader.load(self.loggingQueue, __name__) # Constants self._port = self.moduleConfig['WebsocketPort'] self._host = self.moduleConfig['WebsocketHost'] # logging setup self.logger = ThreadsafeLogger(loggingQueue, __name__) def run(self): """ Main thread entry point. Sets up websocket server and event callbacks. Starts thread to monitor inbound message queue. """ self.logger.info("Starting websocket server") self.alive = True self.listen() self.websocketServer = WebsocketServer(self._port, host=self._host) self.websocketServer.set_fn_new_client(self.new_websocket_client) self.websocketServer.set_fn_message_received( self.websocket_message_received) self.websocketServer.run_forever() def new_websocket_client(self, client, server): """ Client joined callback - called whenever a new client joins. """ self.logger.debug("Client joined") def websocket_message_received(self, client, server, message): """ Message received callback - called whenever a new message is received. """ self.logger.debug('Message received: %s' % message) message = json.loads(message) self.logger.info("message jsond: %s" % message) _msg = Message(topic=message['topic'], sender_id=message['sender_id']) if 'sender_type' in message: _msg.sender_type = message['sender_type'] if 'recipients' in message: _msg.recipients = message['recipients'] if 'extended_data' in message: _msg.extended_data = message['extended_data'] self.put_message(_msg) def listen(self): self.threadProcessQueue = Thread(target=self.process_queue) self.threadProcessQueue.setDaemon(True) self.threadProcessQueue.start() def shutdown(self): """ Handle shutdown message. Close and shutdown websocket server. Join queue processing thread. """ self.logger.info("Shutting down websocket server") try: self.logger.info("Closing websocket") self.websocketServer.server_close() except Exception as e: self.logger.error("Websocket close error : %s " % e) try: self.logger.info("Shutdown websocket") self.websocketServer.shutdown() except Exception as e: self.logger.error("Websocket shutdown error : %s " % e) self.alive = False self.threadProcessQueue.join() time.sleep(1) self.exit = True def handle_message(self, message): """ Send message to listening clients. """ self.websocketServer.send_message_to_all(json.dumps(message.__dict__)) def process_queue(self): """ Monitor queue of messages from main process to this thread. """ while self.alive: if (self.inQueue.empty() == False): try: message = self.inQueue.get(block=False, timeout=1) if message is not None: if (message.topic.upper() == "SHUTDOWN" and message.sender_id.lower() == 'main'): self.logger.debug("SHUTDOWN handled") self.shutdown() else: self.handle_message(message) except Exception as e: self.logger.error("Websocket unable to read queue : %s " % e) else: time.sleep(.25)
class MQTTClientModule(ModuleProcess): """ Threaded MQTT client for processing and publishing outbound messages""" def __init__(self, baseConfig, pInBoundEventQueue, pOutBoundEventQueue, loggingQueue): super(MQTTClientModule, self).__init__() self.config = baseConfig self.alive = True self.inQueue = pInBoundEventQueue # Module config self.moduleConfig = configLoader.load(self.loggingQueue, __name__) # Constants self._keepAlive = self.moduleConfig['MqttKeepAlive'] self._feedName = self.moduleConfig['MqttFeedName'] self._username = self.moduleConfig['MqttUsername'] self._key = self.moduleConfig['MqttKey'] self._host = self.moduleConfig['MqttHost'] self._port = self.moduleConfig['MqttPort'] self._publishJson = self.moduleConfig['MqttPublishJson'] # MQTT setup self._client = mqtt.Client() self._client.username_pw_set(self._username, self._key) self._client.on_connect = self.onConnect self._client.on_disconnect = self.onDisconnect self._client.on_message = self.onMessage self.mqttConnected = False # Logging setup self.logger = ThreadsafeLogger(loggingQueue, "MQTT") def onConnect(self, client, userdata, flags, rc): self.logger.debug('MQTT onConnect called') # Result code 0 is success if rc == 0: self.mqttConnected = True # Subscribe to feed here else: self.logger.error('MQTT failed to connect: %s' % rc) raise RuntimeError('MQTT failed to connect: %s' % rc) def onDisconnect(self, client, userdata, rc): self.logger.debug('MQTT onDisconnect called') self.mqttConnected = False if rc != 0: self.logger.debug('MQTT disconnected unexpectedly: %s' % rc) self.handleReconnect(rc) def onMessage(self, client, userdata, msg): self.logger.debug('MQTT onMessage called for client: %s' % client) def connect(self): """ Connect to MQTT broker Skip calling connect if already connected. """ if self.mqttConnected: return self._client.connect(self._host, port=self._port, keepalive=self._keepAlive) def disconnect(self): """ Check if connected""" if self.mqttConnected: self._client.disconnect() def subscribe(self, feed=False): """Subscribe to feed, defaults to feed specified in config""" if not feed: feed = self._feedName self._client.subscribe('{0}/feeds/{1}'.format(self._username, feed)) def publish(self, value, feed=False): """Publish a value to a feed""" if not feed: feed = self._feedName self._client.publish('{0}/feeds/{1}'.format(self._username, feed), payload=value) def flattenDict(self, aDict): """ Get average of simple dictionary of numerical values """ try: val = float(sum(aDict[key] for key in aDict)) / len(aDict) except Exception as e: self.logger.error('Error flattening dict, returning 0: %s' % e) return 0 return val def publishJsonMessage(self, message): msg_str = self.stringifyMessage(message) self.publish(msg_str) def stringifyMessage(self, message): """ Dump into JSON string """ return json.dumps(message.__dict__).encode('utf8') def processQueue(self): """ Process incoming messages. """ while self.alive: # Pump the loop self._client.loop(timeout=1) if (self.inQueue.empty() == False): try: message = self.inQueue.get(block=False, timeout=1) if message is not None and self.mqttConnected: if (message.topic.upper() == "SHUTDOWN" and message.sender_id.lower() == 'main'): self.logger.debug("SHUTDOWN command handled") self.shutdown() else: # Send message as string or split into channels if self._publishJson: self.publishJsonMessage(message) else: self.publishValues(message) except Exception as e: self.logger.error("MQTT unable to read queue : %s " % e) else: time.sleep(.25) def shutdown(self): self.logger.info("Shutting down") self.alive = False time.sleep(1) self.exit = True def run(self): """ Thread start method""" self.logger.info("Running MQTT") self.connect() self.alive = True # Start queue loop self.processQueue()
class CollectionModule(ModuleProcess): # You can keep these parameters the same, all modules receive the same params # self - reference to self # baseConfig - configuration settings defined in /simplesensor/config/base.conf # (https://github.com/AdobeAtAdobe/SimpleSensor/blob/master/config/base.conf) # pInBoundQueue - messages from handler to this module # pOutBoundQueue - messages from this module to other modules # loggingQueue - logging messages for threadsafe logger def __init__(self, baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue): """ Initialize new CollectionModule instance. """ super(CollectionModule, self).__init__(baseConfig, pInBoundQueue, pOutBoundQueue, loggingQueue) # Most collection modules will follow a similar pattern... # 1. Set up some variables on the self object # Queues self.outQueue = pOutBoundQueue self.inQueue = pInBoundQueue self.loggingQueue = loggingQueue self.threadProcessQueue = None self.alive = False self.context = None self.reader = None # 2. Load the module's configuration file # Configs self.moduleConfig = configLoader.load(self.loggingQueue, __name__) self.config = baseConfig # 3. Set some constants to the self object from config parameters (if you want) self._id = self.moduleConfig['CollectionPointId'] self._type = self.moduleConfig['CollectionPointType'] self._port = self.moduleConfig['ReaderPortNumber'] # 4. Create a threadsafe logger object self.logger = ThreadsafeLogger(loggingQueue, __name__) def run(self): """ Main process method, run when the thread's start() function is called. Starts monitoring inbound messages to this module, and collection logic goes here. For example, you could put a loop with a small delay to keep polling the sensor, etc. When something is detected that's relevant, put a message on the outbound queue. """ # Monitor inbound queue on own thread self.listen() self.alive = True while self.context == None: self.establish_context() while self.alive: while self.reader is None: print('.') self.reader = self.get_reader() if self.reader is None: self.logger.info( 'Waiting for 5 seconds before ' + 'trying to find readers again. Is it plugged in?') time.sleep(5) try: # connect to card card = self.get_card() if card is None or self.reader is None: continue # try: cc_block_msg = [0xFF, 0xB0, 0x00, bytes([3])[0], 0x04] try: cc_block = self.send_transmission(card, cc_block_msg) except Exception as e: self.reader = None continue if (cc_block is None or cc_block[0] != 225): # magic number 0xE1 means data to be read self.reader = None continue data_size = cc_block[ 2] * 8 / 4 # CC[2]*8 is data area size in bytes (/4 for blocks) messages = [] data = [] m_ptr = 4 # pointer to actual card memory location terminate = False errd = False while (m_ptr <= data_size + 4 and not errd): msg = None if m_ptr > 255: byte_one = math.floor(m_ptr / 256) byte_two = m_ptr % (byte_one * 256) msg = [ 0xFF, 0xB0, bytes([byte_one])[0], bytes([byte_two])[0], 0x01 ] else: msg = [0xFF, 0xB0, 0x00, bytes([m_ptr])[0], 0x01] try: block = self.send_transmission(card, msg) except RuntimeError as e: self.logger.error("Error, empty block, reset reader 2") self.reader = None errd = True break # decode TLV header tag, length, f_rem = self.parse_TLV_header(block) if length != 255: m_ptr -= 1 if tag == 'TERMINATOR': terminate = True # now read the block of data into a record m_ptr += 1 data = [] for i in range(int(length / 4)): # working with blocks if errd: break msg = None if m_ptr > 255: byte_one = math.floor(m_ptr / 256) byte_two = m_ptr % (byte_one * 256) msg = [ 0xFF, 0xB0, bytes([byte_one])[0], bytes([byte_two])[0], 0x04 ] else: msg = [0xFF, 0xB0, 0x00, bytes([m_ptr])[0], 0x04] try: block = self.send_transmission(card, msg) except RuntimeError as e: self.logger.error( "Error, empty block, reset reader 3") self.reader = None errd = True break data += block m_ptr += 1 amsg = None if tag == 'NDEF': amsg = self.parse_NDEF_msg(data[f_rem:]) messages.append(amsg) for record in amsg: _TYPE = 0 if record[_TYPE]: terminate = True if terminate: break attendee_id = None event_id = None salutation = None first_name = None last_name = None middle_name = None _TYPE = 0 _ID = 1 _PAYLOAD = 2 for message in messages: for record in message: if record[_TYPE] == 'bcard.net:bcard' and len( record[_PAYLOAD] ) > 40: # to avoid bcard url payloads try: (attendee_id, event_id, salutation, first_name, last_name, middle_name) = self.decode_bcard_payload( record[_PAYLOAD]) except Exception as e: self.logger.error( "Error decoding BCARD payload: {}".format( e)) xdata = { 'attendee_id': attendee_id, 'event_id': event_id, 'salutation': salutation, 'first_name': first_name, 'last_name': last_name, 'middle_name': middle_name } msg = self.build_message(topic='scan_in', extendedData=xdata) self.logger.info('Sending message: {}'.format(msg)) self.put_message(msg) except Exception as e: self.logger.error('Loop error: {}'.format(e)) self.reader = None continue self.reader = None # sleep for a bit to avoid double scanning time.sleep(3) def parse_TLV_header(self, barr): # print('parsing TLV header bytes: ', barr) # return (tag, length (in bytes), [value] (if length != 0x00)) try: tag = None length = None if barr[0] == 0x00: tag = 'NULL' if barr[0] == 0x03: tag = 'NDEF' if barr[0] == 0xFD: tag = 'PROPRIETARY' if barr[0] == 0xFE: tag = 'TERMINATOR' f_rem = 0 if barr[1] != 0xFF: print('NOT USING 3 BYTE FORMAT') length = barr[1] f_rem = 2 else: length = struct.unpack('>h', bytes(barr[2:4]))[0] except Exception as e: self.logger.error('Error parsing TLV header: {}'.format(e)) return 0, 0, 0 return tag, length, f_rem def parse_NDEF_header(self, barr): # parse ndef header # return TNF(type name format), ID_LEN (ID length), # SR (short record bit), CF (chunk flag), # ME (message end), MB (message begin), # TYPE_LEN (type length), PAY_LEN (payload length), # ID (record type indicator) try: TNF = (0b00000111 & barr[0]) ID_LEN = (0b00001000 & barr[0]) >> 3 SR = (0b00010000 & barr[0]) >> 4 CF = (0b00100000 & barr[0]) >> 5 ME = (0b01000000 & barr[0]) >> 6 MB = (0b10000000 & barr[0]) >> 7 TYPE_LEN = barr[1] if SR: PAY_LEN = barr[2] PAY_TYPE = None REC_ID = None if TYPE_LEN > 0: PAY_TYPE = bytearray(barr[3:3 + TYPE_LEN]).decode('UTF-8') if ID_LEN > 0: REC_ID = barr[3 + TYPE_LEN:3 + TYPE_LEN + ID_LEN] else: PAY_LEN = struct.unpack('>I', bytes(barr[2:6]))[0] PAY_TYPE = None if TYPE_LEN > 0: PAY_TYPE = bytearray(barr[6:6 + TYPE_LEN]).decode('UTF-8') REC_ID = None if ID_LEN > 0: REC_ID = barr[6 + TYPE_LEN:6 + TYPE_LEN + ID_LEN] return (TNF, ID_LEN, SR, CF, ME, MB, TYPE_LEN, PAY_LEN, PAY_TYPE, REC_ID) except Exception as e: self.logger.error('Error parsing NDEF header: {}'.format(e)) return (None, None, None, None, None, None, None, None, None, None) def parse_NDEF_msg(self, data): # iterate through the message bytes, reading ndef messages itr = 0 records = [] while itr < len(data): # get header first try: (TNF, ID_LEN, SR, CF, ME, MB, TYPE_LEN, PAY_LEN, PAY_TYPE, REC_ID) = self.parse_NDEF_header(data) if (TNF is None and ID_LEN is None and SR is None and CF is None and ME is None and MB is None and TYPE_LEN is None and PAY_LEN is None and PAY_TYPE is None and REC_ID is None): return [] if SR: itr += 3 + ID_LEN + TYPE_LEN else: itr += 6 + ID_LEN + TYPE_LEN rec_payload = data[itr:itr + PAY_LEN] itr += PAY_LEN record = [PAY_TYPE, REC_ID, rec_payload] # type, id, payload records.append(record) except Exception as e: self.logger.error('Error parsing NDEF message: {}'.format(e)) return records return records def decode_bcard_payload(self, payload): # loop through decoding segments # each field separated by 0x1E # 6 fields: fields = { 'attendee_id': '', 'event_id': '', 'salutation': '', 'first_name': '', 'last_name': '', 'middle_name': '' } field = 0 for b in payload: try: if field == 0 and b == 30: field += 1 continue elif b == 31: field += 1 if field >= len(list(fields.keys())): break continue fields[list(fields.keys())[field]] += bytearray( [b]).decode('UTF8') except: pass return tuple(fields.values()) def establish_context(self): hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER) if hresult != SCARD_S_SUCCESS: self.logger.error('Unable to establish context: {}'.format( SCardGetErrorMessage(hresult))) return self.context = hcontext def get_reader(self): hresult, readers = SCardListReaders(self.context, []) if hresult != SCARD_S_SUCCESS: self.logger.error('Failed to list readers: {}'.format( SCardGetErrorMessage(hresult))) return if len(readers) < 1 or len(readers) - 1 < self._port: self.logger.error( 'Not enough readers attached. {} needed, {} attached'.format( (self._port + 1), (len(readers)))) return else: return readers[self._port] def get_card(self, mode=None, protocol=None): hresult, hcard, dwActiveProtocol = SCardConnect( self.context, self.reader, mode or SCARD_SHARE_SHARED, protocol or (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) if hresult != SCARD_S_SUCCESS: return else: return hcard def send_transmission(self, card, msg, protocol=None): hresult, response = SCardTransmit(card, protocol or SCARD_PCI_T1, msg) if hresult != SCARD_S_SUCCESS: self.logger.error('Failed to send transmission: {}'.format( SCardGetErrorMessage(hresult))) raise RuntimeError("Error sending transmission: {}".format( SCardGetErrorMessage(hresult))) else: return response[:-2] def listen(self): """ Start thread to monitor inbound messages, declare module alive. """ self.threadProcessQueue = Thread(target=self.process_queue) self.threadProcessQueue.setDaemon(True) self.threadProcessQueue.start() def build_message(self, topic, extendedData={}, recipients=['communication_modules']): """ Create a Message instance. topic (required): message type sender_id (required): id property of original sender sender_type (optional): type of sender, ie. collection point type, module name, hostname, etc extended_data (optional): payload to deliver to recipient(s) recipients (optional): module name, which module(s) the message will be delivered to, ie. `websocket_server`. use an array of strings to define multiple modules to send to. use 'all' to send to all available modules. use 'local_only' to send only to modules with `low_cost` prop set to True. [DEFAULT] use 'communication_modules' to send only to communication modules. use 'collection_modules' to send only to collection modules. """ msg = Message(topic=topic, sender_id=self._id, sender_type=self._type, extended_data=extendedData, recipients=recipients, timestamp=datetime.datetime.utcnow()) return msg def put_message(self, msg): """ Put message onto outgoing queue. """ print('putting message: ', msg) self.outQueue.put(msg) def process_queue(self): """ Process inbound messages on separate thread. When a message is encountered, trigger an event to handle it. Sleep for some small amount of time to avoid overloading. Also receives a SHUTDOWN message from the main process when the user presses the esc key. """ self.logger.info( "Starting to watch collection point inbound message queue") while self.alive: if (self.inQueue.empty() == False): self.logger.info("Queue size is %s" % self.inQueue.qsize()) try: message = self.inQueue.get(block=False, timeout=1) if message is not None: self.handle_message(message) except Exception as e: self.logger.error("Error, unable to read queue: %s " % e) self.shutdown() self.logger.info("Queue size is %s after" % self.inQueue.qsize()) else: time.sleep(.25) def handle_message(self, message): """ Handle messages from other modules to this one. Switch on the message topic, do something with the data fields. """ if message.topic.upper() == 'SHUTDOWN' and message.sender_id == 'main': self.shutdown() def shutdown(self): """ Shutdown the collection module. Set alive flag to false so it stops looping. Wait for things to die, then exit. """ self.alive = False print("Shutting down nfc_bcard_reader") time.sleep(1) self.exit = True