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
Exemple #2
0
    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