ACTIVITY_STATE_KEY = 'activityState' BRIGHTNESS_KEY = 'brightness' CAPTURED_TODAY_KEY = 'capturedToday' FLIP_KEY = 'flip' IDLE_SNAPSHOT_PATH = '/hmsweb/users/devices/fullFrameSnapshot' LAST_CAPTURE_KEY = 'lastCapture' LAST_IMAGE_DATA_KEY = 'presignedLastImageData' LAST_IMAGE_KEY = 'presignedLastImageUrl' LAST_IMAGE_SRC_KEY = 'lastImageSource' MEDIA_COUNT_KEY = 'mediaObjectCount' MEDIA_UPLOAD_KEYS = [MEDIA_COUNT_KEY, LAST_IMAGE_KEY] MIRROR_KEY = 'mirror' MOTION_SENS_KEY = 'motionSetupModeSensitivity' POWER_SAVE_KEY = 'powerSaveMode' PRELOAD_DAYS = 30 PRIVACY_KEY = 'privacyActive' RECORD_START_PATH = "/hmsweb/users/devices/startRecord" RECORD_STOP_PATH = "/hmsweb/users/devices/stopRecord" SNAPSHOT_KEY = 'presignedFullFrameSnapshotUrl' SIREN_STATE_KEY = 'sirenState' STREAM_SNAPSHOT_KEY = 'presignedContentUrl' STREAM_SNAPSHOT_PATH = '/hmsweb/users/devices/takeSnapshot' STREAM_START_PATH = '/hmsweb/users/devices/startStream' CAMERA_MEDIA_DELAY = MEDIA_LIBRARY_DELAY + 10 AUDIO_POSITION_KEY = 'position' AUDIO_TRACK_KEY = 'trackId' DEFAULT_TRACK_ID = '229dca67-7e3c-4a5f-8f43-90e1a9bffc38' MEDIA_PLAYER_RESOURCE_ID = "audioPlayback/player" class ArloChildDevice(ArloDevice):

    def __init__(self, name, arlo, attrs):
        super().__init__(name, arlo, attrs)

        self._parent_id = attrs.get('parentId', None)
        self._arlo.debug('parent is {}'.format(self._parent_id))
        self._arlo.debug('resource is {}'.format(self.resource_id))

    @property
    def resource_type(self):
        return "child"

    @property
    def resource_id(self):
        """ Shortcut for mostly used resource id.

        Some devices - certain cameras - can provide other types.
        """
        return self.resource_type + "/" + self._device_id

    @property
    def parent_id(self):
        if self._parent_id is not None:
            # self._arlo.debug('real parent is {}'.format(self._parent_id))
            return self._parent_id
        # self._arlo.debug('fake parent is {}'.format(self.device_id))
        return self.device_id

    @property
    def base_station(self):
        # look for real parents
        for base in self._arlo.base_stations:
            if base.device_id == self.parent_id:
                return base
        # some cameras don't have base stations... it's its own basestation...
        for base in self._arlo.base_stations:
            if base.device_id == self.device_id:
                return base
        # no idea!
        return self._arlo.base_stations[0]

    @property
    def battery_level(self):
        return self._arlo.st.get([self._device_id, BATTERY_KEY], 100)

    @property
    def battery_tech(self):
        return self._arlo.st.get([self._device_id, BATTERY_TECH_KEY], 'None')

    @property
    def charging(self):
        return self._arlo.st.get([self._device_id, CHARGING_KEY], 'off').lower() == 'on'

    @property
    def charger_type(self):
        return self._arlo.st.get([self._device_id, CHARGER_KEY], 'None')

    @property
    def wired(self):
        return self.charger_type.lower() != 'none'

    @property
    def wired_only(self):
        return self.battery_tech.lower() == 'none' and self.wired

    @property
    def signal_strength(self):
        return self._arlo.st.get([self._device_id, SIGNAL_STR_KEY], 3)

    @property
    def is_unavailable(self):
        return self._arlo.st.get([self._device_id, CONNECTION_KEY], 'unknown') == 'unavailable'

    @property
    def too_cold(self):
        return self._arlo.st.get([self._device_id, CONNECTION_KEY], 'unknown') == 'thermalShutdownCold'

    @property
    def state(self):
        if self.is_unavailable:
            return 'unavailable'
        if not self.is_on:
            return 'turned off'
        if self.too_cold:
            return 'offline, too cold'
        return 'idle' def http_get(url, filename=None):
    # make request
    ret = _http_get(url)
    if ret is None:
        return False

    if filename is None:
        return ret.content

    with open(filename, 'wb') as data:
        data.write(ret.content)
    return True def http_get_img(url):
    # make request
    ret = _http_get(url)
    if ret is None:
        return None, datetime.now()

    date = ret.headers.get('Last-Modified', None)
    if date is not None:
        date = httptime_to_datetime(date)
    else:
        date = datetime.now()
    return ret.content, date
import base64
import threading
import time
import zlib
from .constant import (ACTIVITY_STATE_KEY, BRIGHTNESS_KEY,
                       CAPTURED_TODAY_KEY, FLIP_KEY, IDLE_SNAPSHOT_PATH, LAST_CAPTURE_KEY,
                       LAST_IMAGE_DATA_KEY, LAST_IMAGE_KEY,
                       LAST_IMAGE_SRC_KEY, MEDIA_COUNT_KEY,
                       MEDIA_UPLOAD_KEYS, MIRROR_KEY, MOTION_SENS_KEY,
                       POWER_SAVE_KEY, PRELOAD_DAYS, PRIVACY_KEY,
                       RECORD_START_PATH, RECORD_STOP_PATH,
                       SNAPSHOT_KEY, SIREN_STATE_KEY, STREAM_SNAPSHOT_KEY,
                       STREAM_SNAPSHOT_PATH, STREAM_START_PATH, CAMERA_MEDIA_DELAY,
                       AUDIO_POSITION_KEY, AUDIO_TRACK_KEY, DEFAULT_TRACK_ID, MEDIA_PLAYER_RESOURCE_ID)
from .device import ArloChildDevice
from .util import http_get, http_get_img
        self._arlo.debug('reloading cache for ' + self._name)
        count, videos = self._arlo.ml.videos_for(self)
        if videos:
            captured_today = len([video for video in videos if video.created_today])
            last_captured = videos[0].created_at_pretty(self._arlo.cfg.last_format)
        else:
            captured_today = 0
            last_captured = None

        # update local copies
        with self._lock:
            self._cache_count = count
            self._cached_videos = videos

        # signal video up!
        self._save_and_do_callbacks(CAPTURED_TODAY_KEY, captured_today)
        if last_captured is not None:
            self._save_and_do_callbacks(LAST_CAPTURE_KEY, last_captured)
        self._do_callbacks('mediaUploadNotification', True)

    def _clear_snapshot(self):
        # signal to anybody waiting
        with self._lock:
            if self._snapshot_state != 'idle':
                self._arlo.debug('our snapshot finished, signal real state')
                self._snapshot_state = 'idle'
                self._lock.notify_all()

        # signal real mode, safe to call multiple times
        self._save_and_do_callbacks(ACTIVITY_STATE_KEY,
                                    self._arlo.st.get([self._device_id, ACTIVITY_STATE_KEY], 'unknown'))

    def _update_media_and_thumbnail(self):
        self._arlo.debug('getting media image for ' + self.name)
        self._update_media()
        url = None
        with self._lock:
            if self._cached_videos:
                url = self._cached_videos[0].thumbnail_url
        if url is not None:
            self._save(LAST_IMAGE_KEY, url)
            self._update_last_image()

    def _update_last_image(self):
        self._arlo.debug('getting image for ' + self.name)

        # Get image and date, if fails set to blank.
        img, date = http_get_img(self._arlo.st.get([self.device_id, LAST_IMAGE_KEY], None))
        if img is None:
            self._arlo.debug('using blank image for ' + self.name)
            img = self._arlo.blank_image

        # signal up if needed
        date = date.strftime(self._arlo.cfg.last_format)
        self._save(LAST_IMAGE_SRC_KEY, 'capture/' + date)
        self._save_and_do_callbacks(LAST_CAPTURE_KEY, date)
        self._save_and_do_callbacks(LAST_IMAGE_DATA_KEY, img)

        # handle snapshot not being handled...
        self._clear_snapshot()

    def _update_last_image_from_snapshot(self):
        self._arlo.debug('getting image for ' + self.name)

        # Get image and date, if fails ignore.
        img, date = http_get_img(self._arlo.st.get([self.device_id, SNAPSHOT_KEY], None))
        if img is not None:
            date = date.strftime(self._arlo.cfg.last_format)
            self._save(LAST_IMAGE_SRC_KEY, 'snapshot/' + date)
            self._save_and_do_callbacks(LAST_IMAGE_DATA_KEY, img)

        # handle snapshot finished
        self._clear_snapshot()

    def _parse_statistic(self, data, scale):
        """Parse binary statistics returned from the history API"""
        i = 0
        for byte in bytearray(data):
            i = (i << 8) + byte

        if i == 32768:
            return None

        if scale == 0:
            return i

        return float(i) / (scale * 10)

    def _decode_sensor_data(self, properties):
        """Decode, decompress, and parse the data from the history API"""
        b64_input = ""
        for s in properties.get('payload', []):
            # pylint: disable=consider-using-join
            b64_input += s
        if b64_input == "":
            return None

        decoded = base64.b64decode(b64_input)
        data = zlib.decompress(decoded)
        points = []
        i = 0

        while i < len(data):
            points.append({
                'timestamp': int(1e3 * self._parse_statistic(data[i:(i + 4)], 0)),
                'temperature': self._parse_statistic(data[(i + 8):(i + 10)], 1),
                'humidity': self._parse_statistic(data[(i + 14):(i + 16)], 1),
                'airQuality': self._parse_statistic(data[(i + 20):(i + 22)], 1)
            })
            i += 22

        return points[-1]

    def _event_handler(self, resource, event):
        self._arlo.debug(self.name + ' CAMERA got one ' + resource)

        # stream has stopped or recording has stopped
        if resource == 'mediaUploadNotification':

            # look for all possible keys

            for key in MEDIA_UPLOAD_KEYS: