def handle(self, *args, **options):
        for device in PurpleRobotDevice.objects.filter(mute_alerts=False):
            level = device.last_battery()

            if level >= 33:
                cancel_alert(tags='device_last_battery', user_id=device.hash_key)
            elif level >= 25:
                log_alert(message='Battery level is less than 33%.', severity=1, tags='device_last_battery', user_id=device.hash_key)
            else:
                log_alert(message='Battery level is less than 25%.', severity=2, tags='device_last_battery', user_id=device.hash_key)
 def handle(self, *args, **options):
     for device in PurpleRobotDevice.objects.filter(mute_alerts=False):
         pending = device.last_pending_count()
         
         if pending < 250:
             cancel_alert(tags='device_pending_files', user_id=device.hash_key)
         elif pending < 1000:
             log_alert(message=str(pending) + ' files are awaiting upload.', severity=1, tags='device_pending_files', user_id=device.hash_key)
         else:
             log_alert(message=str(pending) + ' files are awaiting upload.', severity=2, tags='device_pending_files', user_id=device.hash_key)
 def handle(self, *args, **options):
     for device in PurpleRobotDevice.objects.all():
         payload = PurpleRobotPayload.objects.filter(user_id=device.hash_key).order_by('-added').first()
         
         now = timezone.now()
         
         if payload == None:
             log_alert(message='No payloads have been uploaded yet.', severity=1, tags='device_last_upload', user_id=device.hash_key)
         elif (now - payload.added).total_seconds() > (60 * 60 * 12):
             log_alert(message='No payloads have been uploaded in the last 12 hours.', severity=2, tags='device_last_upload', user_id=device.hash_key)
         else:
             cancel_alert(tags='device_last_upload', user_id=device.hash_key)
    def handle(self, *args, **options):
        if os.access('/tmp/expected_probe_missing_check.lock', os.R_OK):
            t = os.path.getmtime('/tmp/expected_probe_missing_check.lock')
            created = datetime.datetime.fromtimestamp(t)
            
            if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 4:
                print('expected_probe_missing_check: Stale lock - removing...')
                os.remove('/tmp/expected_probe_missing_check.lock')
            else:
                return
    
        touch('/tmp/expected_probe_missing_check.lock')

        start = timezone.now() - datetime.timedelta(days=START_DAYS)
        
        for device in PurpleRobotDevice.objects.all().order_by('device_id'):
            config = None
            
            default = PurpleRobotConfiguration.objects.filter(slug='default').first()
            
            if device.configuration != None:
                config = device.configuration
            elif device.device_group.configuration != None:
                config = device.device_group.configuration
            elif config == None:
                config = default
            
            if config == None:
                log_alert(message='No configuration associated with ' + device.device_id + '.', severity=2, tags=TAG, user_id=device.hash_key)
            else:
                config_probes = enabled_probes(loads(config.contents, true='#t', false='#f'))
                
                missing_probes = []
                
                for probe in config_probes:
                    found = device.most_recent_reading(probe)
                    
                    if found == None or found.logged < start:
                        missing_probes.append(probe.split('.')[-1])
                        
#                        if found == None:
#                            print(device.device_id + ' ' + str(config) + ': ' + probe)

                        
                if len(missing_probes) == 0:
                    cancel_alert(tags=TAG, user_id=device.hash_key)
                else:
                    log_alert(message='Missing data from ' + str(len(missing_probes)) + ' probe(s). Absent probes: ' + ', '.join(missing_probes), severity=2, tags=TAG, user_id=device.hash_key)

        os.remove('/tmp/expected_probe_missing_check.lock')
 def handle(self, *args, **options):
     for device in PurpleRobotDevice.objects.filter(mute_alerts=False):
         for sensor in HARDWARE_SENSORS:
             last = device.most_recent_reading(sensor)
             
             if last != None:
                 hardwares = set()
                 
                 start = timezone.now() - datetime.timedelta(days=3)
                 
                 for reading in PurpleRobotReading.objects.filter(user_id=device.hash_key, probe=sensor, logged__gte=start):
                     payload = json.loads(reading.payload)
                     
                     if 'SENSOR' in payload:
                         hardwares.add(payload['SENSOR']['NAME'])
                     
                 if len(hardwares) < 2:
                     cancel_alert(tags='device_sensor_changed_' + my_slugify(sensor.replace('edu.northwestern.cbits.purple_robot_manager.', '')), user_id=device.hash_key)
                 else:
                     log_alert(message='Multiple hardware sensors observed for ' + sensor.replace('edu.northwestern.cbits.purple_robot_manager.', '') + '. Multiple devices are likely sharing the same user identifier.', severity=2, tags='device_sensor_changed_' + my_slugify(sensor.replace('edu.northwestern.cbits.purple_robot_manager.', '')), user_id=device.hash_key)
 def handle(self, *args, **options):
     for device in PurpleRobotDevice.objects.filter(mute_alerts=False):
         start = timezone.now() - datetime.timedelta(days=3)
         
         mac = None
         same_mac = True
         
         for reading in PurpleRobotReading.objects.filter(user_id=device.hash_key, probe=HARDWARE_PROBE, logged__gte=start):
             payload = json.loads(reading.payload)
             
             if 'WIFI_MAC' in payload:
                 if mac == None:
                     mac = payload['WIFI_MAC']
                 elif mac != payload['WIFI_MAC']:
                     same_mac = False
         
         if same_mac:
             cancel_alert(tags=TAG, user_id=device.hash_key)
         else:
             log_alert(message='Device hardware changed.', severity=2, tags=TAG, user_id=device.hash_key)
    def handle(self, *args, **options):
        play_url = 'https://play.google.com/store/apps/details?id=edu.northwestern.cbits.purple_robot_manager'

        request = requests.get(play_url)

        soup = BeautifulSoup(request.text)

        if soup is not None:
            changelog = ''

            changes = soup.findAll('div', {'class': 'recent-change'})

            for change in changes:
                if len(changelog) > 0:
                    changelog += '\n'

                changelog += change.contents[0].strip()

            version_html = soup.find('div', {'class': 'content', 'itemprop': 'softwareVersion'})

            version = version_html.contents[0].strip()

            # version_code_html = soup.findAll('button', {'class': 'dropdown-child'})

            # for div in version_code_html:
            # if div.contents[0].strip() == 'Latest Version':
            # version_code = float(div['data-dropdown-value'])

            for device in PurpleRobotDevice.objects.filter(mute_alerts=False):
                device_version = device.config_last_user_agent

                if device_version is None:
                    log_alert(message='Unable to determine installed version.', severity=1, tags=TAG, user_id=device.hash_key)
                elif device_version.endswith(str(version)):
                    cancel_alert(tags=TAG, user_id=device.hash_key)
                else:
                    log_alert(message='Running an older version on Purple Robot: ' + device_version + '.', severity=1, tags=TAG, user_id=device.hash_key)
        else:
            print 'Unable to fetch Play Store metadata.'
 def handle(self, *args, **options):
     start = datetime.datetime.now() - datetime.timedelta(days=START_DAYS)
     
     for device in PurpleRobotDevice.objects.filter(mute_alerts=False):
         payload = PurpleRobotPayload.objects.filter(user_id=device.hash_key, added__gte=start).order_by('-added').first()
         reading = PurpleRobotReading.objects.filter(user_id=device.hash_key, logged__gte=start).order_by('-logged').first()
         
         if payload != None and reading != None:
             diff = payload.added - reading.logged
             
             seconds = diff.total_seconds()
         
             if seconds < (10 * 60):            
                 cancel_alert(tags='device_payload_reading_gap', user_id=device.hash_key)
             elif seconds < (30 * 60):            
                 log_alert(message='{0:.2f}'.format(seconds / 60) + ' minute gap between payload and reading.', severity=1, tags='device_payload_reading_gap', user_id=device.hash_key)
             else:
                 log_alert(message='{0:.2f}'.format(seconds / 60) + ' minute gap between payload and reading.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key)
         elif payload == None:
                 log_alert(message='Unable to determine reading-payload gap. No payloads uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key)
         elif reading == None:
                 log_alert(message='Unable to determine reading-payload gap. No readings uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key)
    def handle(self, *args, **options):
        if os.access('/tmp/expected_probe_missing_check.lock', os.R_OK):
            t = os.path.getmtime('/tmp/expected_probe_missing_check.lock')
            created = datetime.datetime.fromtimestamp(t)
            
            if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 4:
                print('expected_probe_missing_check: Stale lock - removing...')
                os.remove('/tmp/expected_probe_missing_check.lock')
            else:
                return
        
        touch('/tmp/expected_probe_missing_check.lock')

        start = timezone.now() - datetime.timedelta(days=START_DAYS)
        
        for device in PurpleRobotDevice.objects.filter(mute_alerts=False).order_by('device_id'):
            model = device.last_model()
            mfgr = device.last_manufacturer()
            
            config = None
            
            default = PurpleRobotConfiguration.objects.filter(slug='default').first()
            
            if device.configuration != None:
                config = device.configuration
            elif device.device_group != None and device.device_group.configuration != None:
                config = device.device_group.configuration
            elif config == None:
                config = default
            
            if config == None:
                log_alert(message='No configuration associated with ' + device.device_id + '.', severity=2, tags=TAG, user_id=device.hash_key)
            else:
                config_probes = enabled_probes(loads(config.contents, true='#t', false='#f'))
                
                missing_probes = []
                
                for probe in config_probes:
                    if can_sense(mfgr, model, probe):
                        found = device.most_recent_reading(probe)
                    
                        if found == None or found.logged < start:
                            missing_probes.append(probe.split('.')[-1])
                        
#                        if found == None:
#                            print(device.device_id + ' ' + str(config) + ': ' + probe)
                
                platform = device.last_platform()
                
                if platform != None and platform.startswith('Android 5'):
                    if 'ApplicationLaunchProbe' in missing_probes:
                        missing_probes.remove('ApplicationLaunchProbe')
                        
                    if 'RunningSoftwareProbe' in missing_probes:
                        missing_probes.remove('RunningSoftwareProbe')
                        
                if len(missing_probes) == 0:
                    cancel_alert(tags=TAG, user_id=device.hash_key)
                else:
                    missing_probes_str = ', '.join(missing_probes[:4])
                    
                    if len(missing_probes) > 4:
                        missing_probes_str = missing_probes_str + ', and ' + str(len(missing_probes) - 4) + ' more'
                    
                    log_alert(message='Missing data from ' + str(len(missing_probes)) + ' probe(s). Absent probes: ' + missing_probes_str, severity=2, tags=TAG, user_id=device.hash_key)

        os.remove('/tmp/expected_probe_missing_check.lock')