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 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 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 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)
Example #5
0
class ClientRegistry(object):
    onClientRemoved = RegistryEvent()
    onClientAdded = RegistryEvent()
    onClientUpdated = RegistryEvent()
    onSweepComplete = RegistryEvent()

    def __init__(self,collectionPointConfig,loggingQueue):
        # Logger
        self.loggingQueue = loggingQueue
        self.logger = ThreadsafeLogger(loggingQueue, __name__)

        self.rClients = {}  #registered clients
        self.collectionPointConfig = collectionPointConfig

    def getAll(self, thresh=-68):
        """
        Get registered clients as dict
        Only return clients above thresh RSSI
        """
        toret = {}
        for k, v in self.rClients.items():
            xdata = v.getExtendedDataForEvent()
            if xdata['rssi']>thresh:
                toret[k] = xdata
        return toret

    def getUpdateData(self, thresh=-68):
        return {'nearby': self.getAll(thresh)}

    def getClient(self,mac):
        """
        Get an existing registered client by mac 
        and if its found return it. 
        If no existing registered client is found 
        return None.
        """
        try:
            eClient = self.rClients[mac]
        except KeyError:
            eClient = None

        return eClient

    def sweepOldClients(self):
        """
        Look at the registry and look for expired
        and inactive clients.  

        Returns a list of removed clients.
        """
        self.logger.debug("*** Sweeping clients existing count" +
            " %s***"%len(self.rClients))

        clientsToBeRemoved=[] #list of clients to be cleaned up

        clientTimeout = self.collectionPointConfig['AbandonedClientTimeout']
        now = datetime.now()

        for mac in self.rClients:
            regClient = self.rClients[mac]

            # self.logger.debug('now-regClient.lastRegisteredTime ---- clientTimeout >0 : %s ---- %s'%(((now-regClient.lastRegisteredTime).total_seconds()*1000), clientTimeout))
            # if regClient.sweepShouldSendClientOutEvent():
            if (now-regClient.lastRegisteredTime).total_seconds()*1000-clientTimeout>0:
                clientsToBeRemoved.append(regClient)

        for client in clientsToBeRemoved:
            # self.logger.debug("Client sweep removing mac %s"%client.getMac())
            self.removeClient(client)

        self.logger.debug("*** End of sweeping tags existing count "+
            "%s***"%len(self.rClients))

        self.onSweepComplete(clientsToBeRemoved)

        return clientsToBeRemoved

    def addClient(self,client):
        #self.logger.debug("in addNewRegisteredClient with %s"%client.getUdid())
        self.rClients[client.getMac()] = client
        self.onClientAdded(client)

    def updateClient(self,client):
        #self.logger.debug("in updateRegisteredClient with %s"%client.getUdid())
        self.rClients[client.getMac()] = client
        self.onClientUpdated(client)

    def removeClient(self,client):
        #self.logger.debug("in removeRegisteredClient with %s"%client.getUdid())
        self.logger.info('length before remove: %s'%len(self.rClients))
        self.rClients.pop(client.getMac())
        self.logger.info('length after remove: %s'%len(self.rClients))
        self.onClientRemoved(client)
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 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