Example #1
0
 def __init__(self, **kwargs):
     ''' Initializes a new instance of the ShellProcessor
     '''
     super(ShellProcessor, self).__init__()
     self.halt_event = threading.Event()
     self.watcher    = DeviceWatcher(kwargs.get('device_port'), kwargs.get('device_type'))
     self.client     = ShellClient(kwargs.get('service_base'))
     self.twilio     = TwilioRestClient(kwargs.get('twilio_account'), kwargs.get('twilio_token'))
     self.__initialize_caches()
Example #2
0
class ShellProcessor(threading.Thread):
    ''' This is the shell message processing service
    ''' 
    connections    = [] # add/removes are atomic, no locking needed

    def __init__(self, **kwargs):
        ''' Initializes a new instance of the ShellProcessor
        '''
        super(ShellProcessor, self).__init__()
        self.halt_event = threading.Event()
        self.watcher    = DeviceWatcher(kwargs.get('device_port'), kwargs.get('device_type'))
        self.client     = ShellClient(kwargs.get('service_base'))
        self.twilio     = TwilioRestClient(kwargs.get('twilio_account'), kwargs.get('twilio_token'))
        self.__initialize_caches()

    def __initialize_caches(self):
        ''' Initialize the player cache
        '''
        players = self.client.get_all_players()
        self.player_cache  = dict((player['id'], player) for player in players)
        self.reading_cache = {}

    def __send_alert_message(self, player):
        ''' Send an alert message to 

        :param player: The player to send a mesasge to
        '''
        player = self.player_cache[player]
        if (not player.get('contacted', False)) and (len(player['contacts']) > 0):
            result  = True
            params  = (player['firstname'], player['lastname'])
            message = "%s %s experienced a possible tramatic injury, please follow up." % params
            numbers = [contact['phone'] for contact in player['contacts']]
            for number in numbers:
                _logger.info("Sending emergency text to %s", number)
                sms = self.twilio.sms.messages.create(to=number, from_='4155992671', body=message)
                result &= (sms.status != "failed")
            player['contacted'] = result # don't send any more messages if all successful

    def __get_reading_today(self, player):
        ''' Checks to see if we have an existing reading for today

        :param player: The player to check for a current reading for
        :returns: The result of the operation
        '''
        # we are already cached, just return that
        today = str(date.today())
        if self.reading_cache.get(player, {'short_date':''})['short_date'] == today:
            return self.reading_cache[player]

        # we haven't cached yet, get and check latest reading
        reading = self.client.get_latest_reading(player)
        if reading != None:
            reading['short_date'] = reading['date'].split(' ')[0]
            if reading['short_date'] == today:
                self.reading_cache[player] = reading
                return reading

        # we don't have a reading for today, create it
        reading = self.client.generate_empty_reading(player)
        reading = self.client.add_reading(reading)
        reading['short_date'] = today
        self.reading_cache[player] = reading
        return reading

    def __update_player_status(self, player, status):
        ''' A helper method to simply update the player status

        :param player: The player to check for a current reading for
        :returns: The result of the operation
        '''
        reading = self.__get_reading_today(player)
        self.client.update_reading({
            'id'     : reading['id'],
            'status' : PlayerStatus.Emergency,
            'date'   : reading['date'],
            'player' : player,
        })

    def __process_message(self, message):
        ''' Merges the newest reading data and the current value
        in the cache.

        :param message: The message to merge with the cache
        :returns: The merged message
        '''
        if message['type'] == 'reading':
            reading = self.__get_reading_today(message['player'])
            message['id'] = reading['id'] 
            message['hits'] = reading['hits'] = (message['hits'] + int(reading['hits']))
            message['acceleration'] = reading['acceleration'] = (message['acceleration'] + float(reading['acceleration']))
            reading['humidity'] = message['humidity'] 
            reading['temperature'] = message['temperature'] 
        elif message['type'] == 'trauma': pass # the trauma message is merged on the sensor

    def __determine_status(self, message):
        ''' The processing step to determine the status of the player

        The rules are as follows:

        1. If no readings are found, the player status is unknown
        2. If the hits >=25 or the acceleration is >= 100, the status is warning
        3. If the hits >= 50 or the acceleration is >= 200, the status is emergency
        4. Otherwise the status is normal

        :param message: The message to process
        '''
        if message['type'] == 'reading':
            if (message['hits'] >= 50) or (message['acceleration'] > 200) or (message['temperature'] > 100):
                message['status'] = PlayerStatus.Emergency
                self.__send_alert_message(message['player'])
            elif (message['hits'] >= 25) or (message['acceleration'] > 100):
                message['status'] = PlayerStatus.Warning
            else: message['status'] = PlayerStatus.Normal
        elif message['type'] == 'trauma': # a trauma will throw the player into an emergency status
            self.__update_player_status(message['player'], PlayerStatus.Emergency)
            self.__send_alert_message(message['player'])

    def __deliver_message(self, message):
        ''' The processing step to deliver new messages to the clients

        .. note::
        
           We deliver the message first so we can mangle the
           message reference without having to do a deep copy.

        :param message: The message to process
        '''
        client_count = len(ShellProcessor.connections)
        if client_count > 0: # don't waste time if there are no clients
            _logger.debug("delivering message to %d clients" % client_count)
            message = json_serialize(message) + "\r\n"
            for queue in ShellProcessor.connections:
                queue.put_nowait(message)
    
    def __update_database(self, message):
        ''' The processing step to update the database

        :param message: The message to process
        '''
        message_type = message.pop('type', 'unknown')
        if message_type == 'trauma': self.client.add_trauma(message)
        elif message_type == 'reading': self.client.update_reading(message)
        else: _logger.error("Unknown message: " + str(message))

    def stop(self):
        ''' Stop the processing service from running '''
        _logger.info("processing sevice was told to stop")
        self.halt_event.set()
        self.watcher.stop()
    
    def run(self):
        ''' The main processor for new messages '''
        try:
            _logger.info("processing sevice starting")
            message_queue = self.watcher.start()
            for message in iter(message_queue.get, None):
                if self.halt_event.is_set(): raise Exception("Stopping")
                try:
                    _logger.debug(str(message))
                    self.__process_message(message)
                    self.__determine_status(message)
                    self.__deliver_message(message)
                    self.__update_database(message)
                except Exception as ex:
                    _logger.exception("error processing message")
        except Exception as ex:
            _logger.exception("exiting the processing thread")
            self.stop() # just in case...