def get_all_sensor_status_v2(cls, retBatteryLevel=False): ''' Returns 'up' | 'down' status of ALL sensors. Inputs: retBatteryLevel -- True, return battery level if exists. Default false Return: list of sensors, status codes, and optionally battery levels: NOTE: Battery level can be 'None' if no records are found [ [Sensor.Entity, [Status, Codes, battLVL]], [Sensor.Entity, [Status, Codes, None]]... ] See list of status codes ''' # Ret list in form: [[uuid, [status codes], [uuid, [status codes]]]] sensor_status = [] # List of all Sensors sensors = sensor_DAO.get_sensors() # Iterate all sensors and get statuss for sensor in sensors: uuid = sensor.uuid status = cls.get_sensor_status_v2(uuid=uuid, retBatteryLevel=retBatteryLevel) if retBatteryLevel: sensor_status.append([uuid, status[0], status[1]]) else: sensor_status.append([uuid, status]) return sensor_status
def get_unregistered_sensors(cls): ''' Returns list of Juvo target IDs that are not currently in the sensor table ''' unregistered_targets = [] registered_targets = [ sensor.juvo_target for sensor in sensor_DAO.get_sensors(type="bed sensor") ] json = cls.get_sensors() sensors = json["data"]["sensors"] for sensor in sensors: target = sensor["target"] if target not in registered_targets: unregistered_targets.append(target) return unregistered_targets
def get_down_periods_door(cls, uuid, start_dt, end_dt): ''' Returns the down periods for the door sensor NOTE: down/up can only be assigned to a daily/24 hour basis, hard to get more accurate than that Inputs: uuid (str) start_dt (datetime) -- Inclusive end_dt (datetime) -- Inclusive Return List of down time: [[start,end]...] ''' # Split into periods whereby the 10th would refer to 12pm 9th, to 12pm 10th start_date = start_dt.replace(hour=0, minute=0, second=0) - timedelta( days=1) # Expand by 1 day, in case start and end are the same day end_date = end_dt.replace(hour=0, minute=0, second=0) # Get all sensor logs of this uuid between start and end dates logs = sensor_log_DAO.get_logs(uuid=uuid, start_datetime=start_date, end_datetime=end_date) logs.sort(key=lambda x: x.recieved_timestamp) # ASC # Treat bathroom and main door differently location = sensor_DAO.get_sensors(uuid=uuid)[0].location if location == "toilet": if logs == None or len( logs) == 0: # No readings since, send warning return [[ start_date.replace(hour=0, minute=0), end_date.replace(hour=23, minute=59) ]] else: return [] # if no records, consider down if len(logs) == 0: missing_dates = [start_date] else: # Find dates without logs logs_d_only = [ l.recieved_timestamp.replace(hour=0, minute=0, microsecond=0) for l in logs ] min_log_dt = logs_d_only[0] max_log_dt = logs_d_only[-1] full_cal = set(min_log_dt + timedelta(x) for x in range(( max_log_dt - min_log_dt).days)) # All dates between start and end missing_dates = sorted(full_cal - set(logs_d_only)) # Slice out dates with no owner ownership = sensor_DAO.get_ownership_hist(uuid=uuid, start_dt=start_dt, end_dt=end_dt) min_dt = max_dt = datetime.datetime.now() for p in ownership[uuid]: if p[1] != None and (p[1] < min_dt): min_dt = p[1].replace(hour=0, minute=0, microsecond=0) if p[2] != None and (p[2] > max_dt): max_dt = p[2].replace(hour=0, minute=0, microsecond=0) ownerless = [] if min_dt > start_dt: ownerless += [ start_dt + timedelta(x) for x in range((min_dt - start_dt).days) ] if max_dt < end_dt: ownerless += [ max_dt + timedelta(x) for x in range((end_dt - max_dt).days) ] missing_dates = sorted(set(missing_dates) - set(ownerless)) # Fine tune periods without logs, it may just be the case where no one uses the door at all # Assumption: reisdents sleep with doors closed. Therefore use Juvo to fine-tune down_periods = [] for missing in missing_dates: missing_end = missing.replace(hour=12) missing_start = missing_end - timedelta(days=1) # Get owner by period door_ownership = sensor_DAO.get_ownership_hist( uuid=uuid, start_dt=missing_start, end_dt=missing_end) rid_owners = [v[0][0] for k, v in door_ownership.items()] # Get Juvo owner by period ERROR HERE MAKE NEW QUERY juvo_ownership = sensor_DAO.get_ownership_hist( start_dt=missing_start, end_dt=missing_end, type="bed sensor") juvo_uuid = None for k, v in juvo_ownership.items(): if v[0][0] in rid_owners: juvo_uuid = k break if juvo_uuid == None: # This resident didnt own a bed sensor this day down_periods.append([ missing.replace(hour=0, minute=0), missing.replace(hour=23, minute=59) ]) if juvo_uuid != None: # Attempt to find if owner slept this period target = None for s in sensor_DAO.get_sensors(uuid=juvo_uuid): target = s.juvo_target break if target == None: # This resident didnt own a bed sensor this day down_periods.append([ missing.replace(hour=0, minute=0), missing.replace(hour=23, minute=59) ]) # Check if sleep was detected juvo_offset_dt = missing - timedelta( days=1) # i.e. Sleep for 10th = 10th 12pm to 11th 12pm records = JuvoAPI.get_target_sleep_summaries( target, juvo_offset_dt, juvo_offset_dt)['sleep_summaries'] for r in records: total_sleep = r['light'] + r['deep'] + r['awake'] if total_sleep < 0: # Sleep detected, no door detected. assume door is down down_periods.append([ missing.replace(hour=0, minute=0), missing.replace(hour=23, minute=59) ]) return down_periods
def get_sensor_status(cls, uuid, retBatteryLevel=False): ''' Returns 'up' | 'down' status of sensors. For new sensors, Im assuming that at least a single record is sent and thus will trigger true. Inputs: uuid (str) retBatteryLevel (boolean) Return: 1. A List of status codes: See class variables 2. A list of Battery level - None if Juvo - Empty if no records found. e.g. Door sensors only send battery level logs when on low battery - Single value in % i.e. ([1,3], [75]) i.e. ([1] , []) // Battery or newly installed sensor i.e. ([0] , [None]]) // Juvo ''' # AREAS OF IMPROVEMENT # 1. Juvo Bed sensor: Warning triggered if no sleep in the past sleep period 9pm-9am. What about if sleep = 1sec? May also be a problem with elderly # - Maybe look at the breakdown period would be better >> light, deep, awake that kind of thing # 2. What if sensor partially breaks, sysmon sends, but no sensor logs? Dont think this can be detected also ret_codes = [] batt_lvl = None curr_time = datetime.datetime.now() curr_time_m1d = curr_time - datetime.timedelta(days=1) try: sensor = sensor_DAO.get_sensors(uuid=uuid)[0] except IndexError: ret_codes.append(cls.INVALID_SENSOR) return ret_codes uuid = sensor.uuid type = sensor.type isDoor = True if type == "door" else False isMotion = True if type == "motion" else False isBed = True if type == "bed sensor" else False #LEGACY -- "disconnected" only exists in master students' data set. As this was thrown by gateway last_sysmons = sysmon_log_DAO.get_last_sysmon(uuid) if len(last_sysmons) > 0: if last_sysmons[0].key == "disconnected": ret_codes.append(cls.DISCONNECTED) if isDoor: # DOOR SENSOR # Check1: sensor update within 24 hours # we are assuming that the door closes at least once a day past24hrs_data = sensor_log_DAO.get_logs( uuid=uuid, start_datetime=curr_time_m1d, end_datetime=curr_time) if len(past24hrs_data) > 0: ret_codes.append(cls.CHECK_WARN) else: ret_codes.append(cls.OK) elif isMotion: # MOTION SENSOR last_batt = sysmon_log_DAO.get_last_battery_level(uuid=uuid) if last_batt != None: batt_update_period = ( curr_time - last_batt.recieved_timestamp).total_seconds() # Check 1: hourly battery update. aeotec multisensor 6 should send battery level sysmon updates on the hour # This may just be Boon Thais' configs if batt_update_period > (60 * 60 * cls.motion_thresh): ret_codes.append(cls.CHECK_WARN) else: ret_codes.append(cls.OK) elif isBed: # BED SENSOR (JUVO) juvo = JuvoAPI() # Check 1: Sleep logged within past sleep period 9pm - 9am day_timebreak = curr_time.replace(hour=9, minute=0, second=0, microsecond=0) night_timebreak = curr_time.replace(hour=21, minute=0, second=0, microsecond=0) if curr_time > day_timebreak and curr_time < night_timebreak: # If current time is between 9am and 9pm sleeps = juvo.get_total_sleep_by_day(target=sensor.juvo_target, start_date=curr_time_m1d, end_date=curr_time_m1d) if len(sleeps) > 0: ret_codes.append(cls.OK) # sleep recorded else: ret_codes.append(cls.CHECK_WARN) # no sleep recorded else: ret_codes.append(cls.INVALID_SENSOR) # Check2: Battery level if isMotion or isDoor: last_batt = sysmon_log_DAO.get_last_battery_level(uuid=uuid) if last_batt != None: # Sysmon batt event found batt_lvl = last_batt.event if batt_lvl < cls.batt_thresh: ret_codes.append(cls.LOW_BATT) else: # No Sysmon batt event found, door sensor high batt, or newly installed sensor batt_lvl = [] # Return Values if retBatteryLevel: return ret_codes, batt_lvl else: return ret_codes
def get_sensor_status_v2(cls, uuid, retBatteryLevel=False): ''' Returns 'up' | 'down' status of sensors. For new sensors, Im assuming that at least a single record is sent and thus will trigger true. Inputs: uuid (str) retBatteryLevel (boolean) Return: 1. A List of status codes: See class variables 2. A list of Battery level - None if Juvo - Empty if no records found. e.g. Door sensors only send battery level logs when on low battery - Single value in % i.e. ([1,3], [75]) i.e. ([1] , []) // Battery or newly installed sensor i.e. ([0] , [None]]) // Juvo ''' # AREAS OF IMPROVEMENT # 1. Juvo Bed sensor: Warning triggered if no sleep in the past sleep period 9pm-9am. What about if sleep = 1sec? May also be a problem with elderly # - Maybe look at the breakdown period would be better >> light, deep, awake that kind of thing # 2. What if sensor partially breaks, sysmon sends, but no sensor logs? Dont think this can be detected also ret_codes = [] batt_lvl = None curr_time = datetime.datetime.now() curr_time_m1d = curr_time - datetime.timedelta(days=1) try: sensor = sensor_DAO.get_sensors(uuid=uuid)[0] except IndexError: ret_codes.append(cls.INVALID_SENSOR) return ret_codes uuid = sensor.uuid type = sensor.type isDoor = True if type == "door" else False isMotion = True if type == "motion" else False isBed = True if type == "bed sensor" else False #LEGACY -- "disconnected" only exists in master students' data set. As this was thrown by gateway last_sysmons = sysmon_log_DAO.get_last_sysmon(uuid) if len(last_sysmons) > 0: if last_sysmons[0].key == "disconnected": ret_codes.append(cls.DISCONNECTED) if isDoor: # DOOR SENSOR return cls.get_curr_status_door(uuid=uuid, retBatteryLevel=retBatteryLevel) elif isMotion: # MOTION SENSOR return cls.get_curr_status_motion(uuid=uuid, retBatteryLevel=retBatteryLevel) elif isBed: # BED SENSOR (JUVO) return cls.get_curr_status_juvo(target=sensor.juvo_target) else: ret_codes.append(cls.INVALID_SENSOR) return ret_codes