{"action":"created","comment":{"url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments/84017036","pull_request_review_id":4820707,"id":84017036,"diff_hunk":"@@ -0,0 +1,993 @@\n+\"\"\"\n+homeassistant.components.icloud\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+Platform that supports scanning iCloud.\n+\n+For more details about this platform, please refer to the documentation at\n+https://home-assistant.io/components/icloud/\n+\"\"\"\n+import logging\n+import time as time\n+from datetime import datetime, timezone, timedelta\n+from pytz import timezone\n+import pytz\n+from math import floor\n+import random\n+\n+from pyicloud import PyiCloudService\n+from pyicloud.exceptions import PyiCloudFailedLoginException\n+\n+import re\n+from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_NAME\n+from homeassistant.helpers.entity import Entity\n+from homeassistant.helpers.entity import generate_entity_id\n+from homeassistant.components.device_tracker import see\n+from homeassistant.helpers.event import track_state_change\n+from homeassistant.helpers.event import track_time_change\n+from homeassistant.helpers.event import track_point_in_time\n+from homeassistant.helpers.event import track_utc_time_change\n+import homeassistant.util.dt as dt_util\n+from homeassistant.util.location import distance\n+\n+_LOGGER = logging.getLogger(__name__)\n+\n+REQUIREMENTS = ['pyicloud==0.9.1']\n+\n+DEPENDENCIES = ['zone', 'device_tracker']\n+\n+CONF_EVENTS = 'events'\n+DEFAULT_EVENTS = False\n+\n+CONF_COOKIEDIRECTORY = 'cookiedirectory'\n+\n+# entity attributes\n+ATTR_ACCOUNTNAME = 'accountname'\n+ATTR_INTERVAL = 'interval'\n+ATTR_DEVICENAME = 'devicename'\n+ATTR_BATTERY = 'battery'\n+ATTR_DISTANCE = 'distance'\n+ATTR_STARTTIME = 'start_time'\n+ATTR_ENDTIME = 'end_time'\n+ATTR_DURATION = 'duration'\n+ATTR_REMAINING = 'remaining_time'\n+ATTR_DEVICESTATUS = 'device_status'\n+ATTR_LOWPOWERMODE = 'low_power_mode'\n+ATTR_BATTERYSTATUS = 'battery_status'\n+ATTR_LOCATION = 'location'\n+ATTR_FRIENDLY_NAME = 'friendly_name'\n+ATTR_GOOGLE_MAPS_TRAVEL_TIME = 'google_maps_travel_time'\n+ATTR_GOOGLE_MAPS_TRAVEL_TIME_DURATION = 'gmtt_duration'\n+ATTR_GOOGLE_MAPS_TRAVEL_TIME_ORIGIN = 'gmtt_origin'\n+\n+TYPE_CURRENT = 'currentevent'\n+TYPE_NEXT = 'nextevent'\n+\n+ICLOUDTRACKERS = {}\n+\n+DOMAIN = 'icloud'\n+DOMAIN2 = 'idevice'\n+DOMAIN3 = 'ievent'\n+\n+ENTITY_ID_FORMAT_ICLOUD = DOMAIN + '.{}'\n+ENTITY_ID_FORMAT_DEVICE = DOMAIN2 + '.{}'\n+ENTITY_ID_FORMAT_EVENT = DOMAIN3 + '.{}'\n+\n+DEVICESTATUSSET = ['features', 'maxMsgChar', 'darkWake', 'fmlyShare',\n+                   'deviceStatus', 'remoteLock', 'activationLocked',\n+                   'deviceClass', 'id', 'deviceModel', 'rawDeviceModel',\n+                   'passcodeLength', 'canWipeAfterLock', 'trackingInfo',\n+                   'location', 'msg', 'batteryLevel', 'remoteWipe',\n+                   'thisDevice', 'snd', 'prsId', 'wipeInProgress',\n+                   'lowPowerMode', 'lostModeEnabled', 'isLocating',\n+                   'lostModeCapable', 'mesg', 'name', 'batteryStatus',\n+                   'lockedTimestamp', 'lostTimestamp', 'locationCapable',\n+                   'deviceDisplayName', 'lostDevice', 'deviceColor',\n+                   'wipedTimestamp', 'modelDisplayName', 'locationEnabled',\n+                   'isMac', 'locFoundEnabled']\n+\n+\n+def setup(hass, config):\n+    \"\"\" Set up the iCloud Scanner. \"\"\"\n+\n+    if config.get(DOMAIN) is None:\n+        return False\n+\n+    for account, account_config in config[DOMAIN].items():\n+\n+        if not isinstance(account_config, dict):\n+            _LOGGER.error(\"Missing configuration data for account %s\", account)\n+            continue\n+\n+        if CONF_USERNAME not in account_config:\n+            _LOGGER.error(\"Missing username for account %s\", account)\n+            continue\n+\n+        if CONF_PASSWORD not in account_config:\n+            _LOGGER.error(\"Missing password for account %s\", account)\n+            continue\n+\n+        # Get the username and password from the configuration\n+        username = account_config.get(CONF_USERNAME)\n+        password = account_config.get(CONF_PASSWORD)\n+        cookiedirectory = account_config.get(CONF_COOKIEDIRECTORY, None)\n+\n+        ignored_devices = []\n+        if 'ignored_devices' in account_config:\n+            ignored_dev = account_config.get('ignored_devices')\n+            for each_dev in ignored_dev:\n+                ignored_devices.append(each_dev)\n+\n+        getevents = account_config.get(CONF_EVENTS, DEFAULT_EVENTS)\n+\n+        googletraveltime = {}\n+        if 'googletraveltime' in account_config:\n+            gttconfig = account_config.get('googletraveltime')\n+            for google, googleconfig in gttconfig.items():\n+                googletraveltime[google] = googleconfig\n+\n+        icloudaccount = Icloud(hass, username, password, cookiedirectory,\n+                               account, ignored_devices, getevents,\n+                               googletraveltime)\n+        icloudaccount.update_ha_state()\n+        ICLOUDTRACKERS[account] = icloudaccount\n+        if ICLOUDTRACKERS[account].api is not None:\n+            for device in ICLOUDTRACKERS[account].devices:\n+                iclouddevice = ICLOUDTRACKERS[account].devices[device]\n+                devicename = iclouddevice.devicename.lower()\n+                track_state_change(hass,\n+                                   'device_tracker.' + devicename,\n+                                   iclouddevice.devicechanged)\n+\n+        if 'manual_update' in account_config:\n+            def update_now(now):\n+                ICLOUDTRACKERS[account].update_icloud(see)\n+\n+            manual_update = account_config.get('manual_update')\n+            for each_time in manual_update:\n+                each_time = dt_util.parse_time(each_time)\n+                track_time_change(hass, update_now,\n+                                  hour=each_time.hour,\n+                                  minute=each_time.minute,\n+                                  second=each_time.second)\n+\n+    if not ICLOUDTRACKERS:\n+        _LOGGER.error(\"No ICLOUDTRACKERS added\")\n+        return False\n+\n+    randomseconds = random.randint(10, 59)\n+\n+    def lost_iphone(call):\n+        \"\"\" Calls the lost iphone function if the device is found \"\"\"\n+        accountname = call.data.get('accountname')\n+        devicename = call.data.get('devicename')\n+        if accountname is None:\n+            for account in ICLOUDTRACKERS:\n+                ICLOUDTRACKERS[account].lost_iphone(devicename)\n+        elif accountname in ICLOUDTRACKERS:\n+            ICLOUDTRACKERS[accountname].lost_iphone(devicename)\n+\n+    hass.services.register(DOMAIN, 'lost_iphone',\n+                           lost_iphone)\n+\n+    def update_icloud(call):\n+        \"\"\" Calls the update function of an icloud account \"\"\"\n+        accountname = call.data.get('accountname')\n+        devicename = call.data.get('devicename')\n+        if accountname is None:\n+            for account in ICLOUDTRACKERS:\n+                ICLOUDTRACKERS[account].update_icloud(see, devicename)\n+        elif accountname in ICLOUDTRACKERS:\n+            ICLOUDTRACKERS[accountname].update_icloud(see, devicename)\n+    hass.services.register(DOMAIN,\n+                           'update_icloud', update_icloud)\n+\n+    def keep_alive(now):\n+        \"\"\" Keeps the api logged in of all account \"\"\"\n+        for accountname in ICLOUDTRACKERS:\n+            try:\n+                ICLOUDTRACKERS[accountname].keep_alive()\n+            except ValueError:\n+                _LOGGER.error(\"something went wrong for account %s, \" +\n+                             \"retrying in a minute\", accountname)\n+\n+    track_utc_time_change(\n+        hass, keep_alive,\n+        second=randomseconds\n+    )\n+\n+    def setinterval(call):\n+        \"\"\" Calls the update function of an icloud account \"\"\"\n+        accountname = call.data.get('accountname')\n+        interval = call.data.get('interval')\n+        devicename = call.data.get('devicename')\n+        if accountname is None:\n+            for account in ICLOUDTRACKERS:\n+                ICLOUDTRACKERS[account].setinterval(interval, devicename)\n+        elif accountname in ICLOUDTRACKERS:\n+            ICLOUDTRACKERS[accountname].setinterval(interval, devicename)\n+\n+    hass.services.register(DOMAIN,\n+                           'setinterval', setinterval)\n+\n+    # Tells the bootstrapper that the component was successfully initialized\n+    return True\n+\n+class IDevice(Entity):  # pylint: disable=too-many-instance-attributes\n+    \"\"\" Represents a Proximity in Home Assistant. \"\"\"\n+    def __init__(self, hass, icloudobject, name, identifier, googletraveltime):\n+        # pylint: disable=too-many-arguments\n+        self.hass = hass\n+        self.icloudobject = icloudobject\n+        self.devicename = name\n+        self.identifier = identifier\n+        self._request_interval_seconds = 60\n+        self._interval = 1\n+        self.api = icloudobject.api\n+        self._distance = None\n+        self._battery = None\n+        self._overridestate = None\n+        self._devicestatuscode = None\n+        self._devicestatus = None\n+        self._lowPowerMode = None\n+        self._batteryStatus = None\n+        self._googletraveltime = googletraveltime\n+        self._gttduration = None\n+        self._gttorigin = None\n+\n+        self.entity_id = generate_entity_id(\n+            ENTITY_ID_FORMAT_DEVICE, self.devicename,\n+            hass=self.hass)\n+\n+    @property\n+    def state(self):\n+        \"\"\" returns the state of the icloud tracker \"\"\"\n+        return self._interval\n+\n+    @property\n+    def unit_of_measurement(self):\n+        \"\"\" Unit of measurement of this entity \"\"\"\n+        return \"minutes\"\n+\n+    @property\n+    def state_attributes(self):\n+        \"\"\" returns the friendlyname of the icloud tracker \"\"\"\n+        if self._googletraveltime is None:\n+            return {\n+                ATTR_DEVICENAME: self.devicename,\n+                ATTR_BATTERY: self._battery,\n+                ATTR_DISTANCE: self._distance,\n+                ATTR_DEVICESTATUS: self._devicestatus,\n+                ATTR_LOWPOWERMODE: self._lowPowerMode,\n+                ATTR_BATTERYSTATUS: self._batteryStatus,\n+                ATTR_GOOGLE_MAPS_TRAVEL_TIME: self._googletraveltime\n+            }\n+        else:\n+            return {\n+                ATTR_DEVICENAME: self.devicename,\n+                ATTR_BATTERY: self._battery,\n+                ATTR_DISTANCE: self._distance,\n+                ATTR_DEVICESTATUS: self._devicestatus,\n+                ATTR_LOWPOWERMODE: self._lowPowerMode,\n+                ATTR_BATTERYSTATUS: self._batteryStatus,\n+                ATTR_GOOGLE_MAPS_TRAVEL_TIME: self._googletraveltime,\n+                ATTR_GOOGLE_MAPS_TRAVEL_TIME_DURATION:\n+                    self._gttduration,\n+                ATTR_GOOGLE_MAPS_TRAVEL_TIME_ORIGIN:\n+                    self._gttorigin\n+            }\n+\n+    @property\n+    def icon(self):\n+        \"\"\"Return the icon to use for device if any.\"\"\"\n+        return 'mdi:cellphone-iphone'\n+\n+    def keep_alive(self):\n+        \"\"\" Keeps the api alive \"\"\"\n+        currentminutes = dt_util.now().hour * 60 + dt_util.now().minute\n+        if currentminutes % self._interval == 0:\n+            self.update_icloud(see)\n+        elif self._interval > 10 and currentminutes % self._interval == 2:\n+            self.update_icloud(see)\n+        elif self._interval > 10 and currentminutes % self._interval == 4:\n+            self.update_icloud(see)\n+\n+        self._gttduration = None\n+        self._gttorigin = None\n+        if self._googletraveltime is not None:\n+            gttstate = self.hass.states.get(self._googletraveltime)\n+            if gttstate is not None:\n+                if 'origin_addresses' in gttstate.attributes:\n+                    self._gttduration = gttstate.state\n+                    self._gttorigin = gttstate.attributes['origin_addresses']\n+                    self.update_ha_state()\n+\n+    def lost_iphone(self):\n+        \"\"\" Calls the lost iphone function if the device is found \"\"\"\n+        if self.api is not None:\n+            self.api.authenticate()\n+            self.identifier.play_sound()\n+\n+    def data_is_accurate(self, data):\n+        if not data:\n+            return False\n+        elif not data['locationFinished']:\n+            return False\n+        return True\n+\n+    def update_icloud(self, see):\n+        \"\"\" Authenticate against iCloud and scan for devices. \"\"\"\n+        if self.api is not None:\n+            from pyicloud.exceptions import PyiCloudNoDevicesException\n+\n+            try:\n+                # Loop through every device registered with the iCloud account\n+                status = self.identifier.status(DEVICESTATUSSET)\n+                dev_id = re.sub(r\"(\\s|\\W|')\", '',\n+                                status['name']).lower()\n+                self._devicestatuscode = status['deviceStatus']\n+                if self._devicestatuscode == '200':\n+                    self._devicestatus = 'online'\n+                elif self._devicestatuscode == '201':\n+                    self._devicestatus = 'offline'\n+                elif self._devicestatuscode == '203':\n+                    self._devicestatus = 'pending'\n+                elif self._devicestatuscode == '204':\n+                    self._devicestatus = 'unregistered'\n+                else:\n+                    self._devicestatus = 'error'\n+                self._lowPowerMode = status['lowPowerMode']\n+                self._batteryStatus = status['batteryStatus']\n+                self.update_ha_state()\n+                status = self.identifier.status(DEVICESTATUSSET)\n+                battery = status['batteryLevel']*100\n+                location = status['location']\n+                if location:\n+                    see(\n+                        hass=self.hass,\n+                        dev_id=dev_id,\n+                        host_name=status['name'],\n+                        gps=(location['latitude'],\n+                             location['longitude']),\n+                        battery=battery,\n+                        gps_accuracy=location['horizontalAccuracy']\n+                    )\n+            except PyiCloudNoDevicesException:\n+                _LOGGER.error('No iCloud Devices found!')\n+\n+    def get_default_interval(self):\n+        devid = 'device_tracker.' + self.devicename\n+        devicestate = self.hass.states.get(devid)\n+        self._overridestate = None\n+        self.devicechanged(self.devicename, None, devicestate)\n+\n+    def setinterval(self, interval=None):\n+        if interval is not None:\n+            devid = 'device_tracker.' + self.devicename\n+            devicestate = self.hass.states.get(devid)\n+            if devicestate is not None:\n+                self._overridestate = devicestate.state\n+            self._interval = interval\n+        else:\n+            self.get_default_interval()\n+        self.update_ha_state()\n+        self.update_icloud(see)\n+\n+    def devicechanged(self, entity, old_state, new_state):\n+        if entity is None:\n+            return\n+\n+        self._distance = None\n+        if 'latitude' in new_state.attributes:\n+            device_state_lat = new_state.attributes['latitude']\n+            device_state_long = new_state.attributes['longitude']\n+            zone_state = self.hass.states.get('zone.home')\n+            zone_state_lat = zone_state.attributes['latitude']\n+            zone_state_long = zone_state.attributes['longitude']\n+            self._distance = distance(device_state_lat, device_state_long,\n+                                      zone_state_lat, zone_state_long)\n+            self._distance = round(self._distance / 1000, 1)\n+        if 'battery' in new_state.attributes:\n+            self._battery = new_state.attributes['battery']\n+\n+        if new_state.state == self._overridestate:\n+            self.update_ha_state()\n+            return\n+\n+        self._overridestate = None\n+\n+        if new_state.state != 'not_home':\n+            self._interval = 30\n+            self.update_ha_state()\n+        else:\n+            if self._distance is None:\n+                self.update_ha_state()\n+                return\n+            if self._distance > 100:\n+                self._interval = round(self._distance, 0)\n+                if self._googletraveltime is not None:\n+                    gttstate = self.hass.states.get(self._googletraveltime)\n+                    if gttstate is not None:\n+                        self._interval = round(float(gttstate.state) - 10, 0)\n+            elif self._distance > 50:\n+                self._interval = 30\n+            elif self._distance > 25:\n+                self._interval = 15\n+            elif self._distance > 10:\n+                self._interval = 5\n+            else:\n+                self._interval = 1\n+            if self._battery is not None:\n+                if self._battery <= 33 and self._distance > 3:\n+                    self._interval = self._interval * 2\n+            self.update_ha_state()\n+\n+class IEvent(Entity):  # pylint: disable=too-many-instance-attributes\n+    \"\"\" Represents a Proximity in Home Assistant. \"\"\"\n+    def __init__(self, hass, icloudobject, name, type=None):\n+        # pylint: disable=too-many-arguments\n+        self.hass = hass\n+        self.icloudobject = icloudobject\n+        self.api = icloudobject.api\n+        self.eventguid = name\n+        self._starttime = None\n+        self._starttext = None\n+        self._endtime = None\n+        self._endtext = None\n+        self._duration = None\n+        self._title = None\n+        self._remaining = 0\n+        self._remainingtext = None\n+        self._location = None\n+        self._type = type\n+        self._tz = None\n+\n+        self.entity_id = generate_entity_id(\n+            ENTITY_ID_FORMAT_EVENT, self.eventguid,\n+            hass=self.hass)\n+\n+    @property\n+    def state(self):\n+        \"\"\" returns the state of the icloud tracker \"\"\"\n+        return self._title\n+\n+    @property\n+    def state_attributes(self):\n+        \"\"\" returns the friendlyname of the icloud tracker \"\"\"\n+        return {\n+            ATTR_STARTTIME: self._starttext,\n+            ATTR_ENDTIME: self._endtext,\n+            ATTR_DURATION: self._duration,\n+            ATTR_REMAINING: self._remainingtext,\n+            ATTR_LOCATION: self._location,\n+            ATTR_FRIENDLY_NAME: self._type\n+        }\n+\n+    @property\n+    def icon(self):\n+        \"\"\"Return the icon to use for device if any.\"\"\"\n+        if self._type == TYPE_CURRENT:\n+            return 'mdi:calendar'\n+        elif self._type == TYPE_NEXT:\n+            return 'mdi:calendar-clock'\n+\n+    def keep_alive(self, starttime, endtime, duration, title, tz, location):\n+        \"\"\" Keeps the api alive \"\"\"\n+        current = self._type == TYPE_CURRENT\n+        nextev = self._type == TYPE_NEXT\n+        self._remaining = 0\n+        tempnow = dt_util.now(tz)\n+        if tz is None:\n+            self._tz = pytz.utc\n+        else:\n+            self._tz = tz\n+\n+        if starttime is None:\n+            self._starttime = None\n+            self._starttext = None\n+        else:\n+            self._starttime = datetime(starttime[1], starttime[2],\n+                                       starttime[3], starttime[4],\n+                                       starttime[5], 0, 0, self._tz)\n+            self._starttext = self._starttime.strftime(\"%A %d %B %Y %H.%M.%S\")\n+            if nextev:\n+                self._remaining = self._starttime - tempnow\n+                remainingdays = self._remaining.days\n+                remainingseconds = (self._starttime.hour * 3600 +\n+                                    self._starttime.minute * 60 +\n+                                    self._starttime.second -\n+                                    tempnow.hour * 3600 -\n+                                    tempnow.minute * 60 -\n+                                    tempnow.second)\n+                if ((self._starttime.year > tempnow.year or\n+                    self._starttime.month > tempnow.month or\n+                    self._starttime.day > tempnow.day) and\n+                    remainingseconds < 0):","path":"homeassistant/components/icloud.py","position":504,"original_position":504,"commit_id":"b849d04b1d11fdf190fa41017a204a2c195d72dc","original_commit_id":"b849d04b1d11fdf190fa41017a204a2c195d72dc","user":{"login":"balloobbot","id":16367972,"avatar_url":"https://avatars.githubusercontent.com/u/16367972?v=3","gravatar_id":"","url":"https://api.github.com/users/balloobbot","html_url":"https://github.com/balloobbot","followers_url":"https://api.github.com/users/balloobbot/followers","following_url":"https://api.github.com/users/balloobbot/following{/other_user}","gists_url":"https://api.github.com/users/balloobbot/gists{/gist_id}","starred_url":"https://api.github.com/users/balloobbot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/balloobbot/subscriptions","organizations_url":"https://api.github.com/users/balloobbot/orgs","repos_url":"https://api.github.com/users/balloobbot/repos","events_url":"https://api.github.com/users/balloobbot/events{/privacy}","received_events_url":"https://api.github.com/users/balloobbot/received_events","type":"User","site_admin":false},"body":"_[farcy v1.1](https://github.com/appfolio/farcy)_\n* 21: E129 visually indented line with same indent as next logical line","created_at":"2016-10-19T08:13:31Z","updated_at":"2016-10-19T08:13:31Z","html_url":"https://github.com/home-assistant/home-assistant/pull/3946#discussion_r84017036","pull_request_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946","_links":{"self":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments/84017036"},"html":{"href":"https://github.com/home-assistant/home-assistant/pull/3946#discussion_r84017036"},"pull_request":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946"}}},"pull_request":{"url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946","id":89960526,"html_url":"https://github.com/home-assistant/home-assistant/pull/3946","diff_url":"https://github.com/home-assistant/home-assistant/pull/3946.diff","patch_url":"https://github.com/home-assistant/home-assistant/pull/3946.patch","issue_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/3946","number":3946,"state":"open","locked":false,"title":"change icloud device_tracker into new platform","user":{"login":"Bart274","id":1118630,"avatar_url":"https://avatars.githubusercontent.com/u/1118630?v=3","gravatar_id":"","url":"https://api.github.com/users/Bart274","html_url":"https://github.com/Bart274","followers_url":"https://api.github.com/users/Bart274/followers","following_url":"https://api.github.com/users/Bart274/following{/other_user}","gists_url":"https://api.github.com/users/Bart274/gists{/gist_id}","starred_url":"https://api.github.com/users/Bart274/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Bart274/subscriptions","organizations_url":"https://api.github.com/users/Bart274/orgs","repos_url":"https://api.github.com/users/Bart274/repos","events_url":"https://api.github.com/users/Bart274/events{/privacy}","received_events_url":"https://api.github.com/users/Bart274/received_events","type":"User","site_admin":false},"body":"**Description:**\r\nThis PR changes the old icloud device_tracker into a new platform.\r\nThis adds the following features to the new platform:\r\n- Each device has it's own dynamic interval instead of one fixed interval for all devices linked to one account\r\n- This adds the ability to ignore specific devices that you don't want to track, so their battery isn't drained\r\n- This adds the possiblity to view the events from your icloud calendar in HA\r\n- This adds the ability to update the devices on a fixed time no matter what the interval is at that time\r\n- This adds 3 new services: one to call the update manually once, one to play the lost iphone sound on a device and one to change the interval.\r\n\r\n**Example entry for `configuration.yaml` (if applicable):**\r\n```yaml\r\nicloud:\r\n  bart:\r\n    username: <my username>\r\n    password: <my password>\r\n    manual_update:\r\n      - '7:45'\r\n      - '8:00'\r\n      - '17:45'\r\n      - '18:00'\r\n    ignored_devices:\r\n      - iphonevanbvb\r\n      - ipodvanbart\r\n    googletraveltime:\r\n      iphonebart: sensor.bart_naar_huis\r\n    events: True\r\n  sharon:\r\n    username: <gf's username>\r\n    password: <gf's password>\r\n    manual_update:\r\n      - '8:45'\r\n      - '9:00'\r\n      - '18:45'\r\n      - '19:00'\r\n```","created_at":"2016-10-19T08:12:36Z","updated_at":"2016-10-19T08:13:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c0339dff40a9484e1398a37951cc58d06a1d51b3","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946/commits","review_comments_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946/comments","review_comment_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments{/number}","comments_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/3946/comments","statuses_url":"https://api.github.com/repos/home-assistant/home-assistant/statuses/b849d04b1d11fdf190fa41017a204a2c195d72dc","head":{"label":"Bart274:new_icloud_platform","ref":"new_icloud_platform","sha":"b849d04b1d11fdf190fa41017a204a2c195d72dc","user":{"login":"Bart274","id":1118630,"avatar_url":"https://avatars.githubusercontent.com/u/1118630?v=3","gravatar_id":"","url":"https://api.github.com/users/Bart274","html_url":"https://github.com/Bart274","followers_url":"https://api.github.com/users/Bart274/followers","following_url":"https://api.github.com/users/Bart274/following{/other_user}","gists_url":"https://api.github.com/users/Bart274/gists{/gist_id}","starred_url":"https://api.github.com/users/Bart274/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Bart274/subscriptions","organizations_url":"https://api.github.com/users/Bart274/orgs","repos_url":"https://api.github.com/users/Bart274/repos","events_url":"https://api.github.com/users/Bart274/events{/privacy}","received_events_url":"https://api.github.com/users/Bart274/received_events","type":"User","site_admin":false},"repo":{"id":71329112,"name":"home-assistant","full_name":"Bart274/home-assistant","owner":{"login":"Bart274","id":1118630,"avatar_url":"https://avatars.githubusercontent.com/u/1118630?v=3","gravatar_id":"","url":"https://api.github.com/users/Bart274","html_url":"https://github.com/Bart274","followers_url":"https://api.github.com/users/Bart274/followers","following_url":"https://api.github.com/users/Bart274/following{/other_user}","gists_url":"https://api.github.com/users/Bart274/gists{/gist_id}","starred_url":"https://api.github.com/users/Bart274/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Bart274/subscriptions","organizations_url":"https://api.github.com/users/Bart274/orgs","repos_url":"https://api.github.com/users/Bart274/repos","events_url":"https://api.github.com/users/Bart274/events{/privacy}","received_events_url":"https://api.github.com/users/Bart274/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Bart274/home-assistant","description":":house_with_garden: Open-source home automation platform running on Python 3","fork":true,"url":"https://api.github.com/repos/Bart274/home-assistant","forks_url":"https://api.github.com/repos/Bart274/home-assistant/forks","keys_url":"https://api.github.com/repos/Bart274/home-assistant/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Bart274/home-assistant/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Bart274/home-assistant/teams","hooks_url":"https://api.github.com/repos/Bart274/home-assistant/hooks","issue_events_url":"https://api.github.com/repos/Bart274/home-assistant/issues/events{/number}","events_url":"https://api.github.com/repos/Bart274/home-assistant/events","assignees_url":"https://api.github.com/repos/Bart274/home-assistant/assignees{/user}","branches_url":"https://api.github.com/repos/Bart274/home-assistant/branches{/branch}","tags_url":"https://api.github.com/repos/Bart274/home-assistant/tags","blobs_url":"https://api.github.com/repos/Bart274/home-assistant/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Bart274/home-assistant/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Bart274/home-assistant/git/refs{/sha}","trees_url":"https://api.github.com/repos/Bart274/home-assistant/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Bart274/home-assistant/statuses/{sha}","languages_url":"https://api.github.com/repos/Bart274/home-assistant/languages","stargazers_url":"https://api.github.com/repos/Bart274/home-assistant/stargazers","contributors_url":"https://api.github.com/repos/Bart274/home-assistant/contributors","subscribers_url":"https://api.github.com/repos/Bart274/home-assistant/subscribers","subscription_url":"https://api.github.com/repos/Bart274/home-assistant/subscription","commits_url":"https://api.github.com/repos/Bart274/home-assistant/commits{/sha}","git_commits_url":"https://api.github.com/repos/Bart274/home-assistant/git/commits{/sha}","comments_url":"https://api.github.com/repos/Bart274/home-assistant/comments{/number}","issue_comment_url":"https://api.github.com/repos/Bart274/home-assistant/issues/comments{/number}","contents_url":"https://api.github.com/repos/Bart274/home-assistant/contents/{+path}","compare_url":"https://api.github.com/repos/Bart274/home-assistant/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Bart274/home-assistant/merges","archive_url":"https://api.github.com/repos/Bart274/home-assistant/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Bart274/home-assistant/downloads","issues_url":"https://api.github.com/repos/Bart274/home-assistant/issues{/number}","pulls_url":"https://api.github.com/repos/Bart274/home-assistant/pulls{/number}","milestones_url":"https://api.github.com/repos/Bart274/home-assistant/milestones{/number}","notifications_url":"https://api.github.com/repos/Bart274/home-assistant/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Bart274/home-assistant/labels{/name}","releases_url":"https://api.github.com/repos/Bart274/home-assistant/releases{/id}","deployments_url":"https://api.github.com/repos/Bart274/home-assistant/deployments","created_at":"2016-10-19T07:10:43Z","updated_at":"2016-10-19T07:10:46Z","pushed_at":"2016-10-19T08:08:06Z","git_url":"git://github.com/Bart274/home-assistant.git","ssh_url":"git@github.com:Bart274/home-assistant.git","clone_url":"https://github.com/Bart274/home-assistant.git","svn_url":"https://github.com/Bart274/home-assistant","homepage":"https://home-assistant.io","size":41880,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"dev"}},"base":{"label":"home-assistant:dev","ref":"dev","sha":"5799d1aec9a80c0f650b7e9ec7556b59d9ab8446","user":{"login":"home-assistant","id":13844975,"avatar_url":"https://avatars.githubusercontent.com/u/13844975?v=3","gravatar_id":"","url":"https://api.github.com/users/home-assistant","html_url":"https://github.com/home-assistant","followers_url":"https://api.github.com/users/home-assistant/followers","following_url":"https://api.github.com/users/home-assistant/following{/other_user}","gists_url":"https://api.github.com/users/home-assistant/gists{/gist_id}","starred_url":"https://api.github.com/users/home-assistant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/home-assistant/subscriptions","organizations_url":"https://api.github.com/users/home-assistant/orgs","repos_url":"https://api.github.com/users/home-assistant/repos","events_url":"https://api.github.com/users/home-assistant/events{/privacy}","received_events_url":"https://api.github.com/users/home-assistant/received_events","type":"Organization","site_admin":false},"repo":{"id":12888993,"name":"home-assistant","full_name":"home-assistant/home-assistant","owner":{"login":"home-assistant","id":13844975,"avatar_url":"https://avatars.githubusercontent.com/u/13844975?v=3","gravatar_id":"","url":"https://api.github.com/users/home-assistant","html_url":"https://github.com/home-assistant","followers_url":"https://api.github.com/users/home-assistant/followers","following_url":"https://api.github.com/users/home-assistant/following{/other_user}","gists_url":"https://api.github.com/users/home-assistant/gists{/gist_id}","starred_url":"https://api.github.com/users/home-assistant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/home-assistant/subscriptions","organizations_url":"https://api.github.com/users/home-assistant/orgs","repos_url":"https://api.github.com/users/home-assistant/repos","events_url":"https://api.github.com/users/home-assistant/events{/privacy}","received_events_url":"https://api.github.com/users/home-assistant/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/home-assistant/home-assistant","description":":house_with_garden: Open-source home automation platform running on Python 3","fork":false,"url":"https://api.github.com/repos/home-assistant/home-assistant","forks_url":"https://api.github.com/repos/home-assistant/home-assistant/forks","keys_url":"https://api.github.com/repos/home-assistant/home-assistant/keys{/key_id}","collaborators_url":"https://api.github.com/repos/home-assistant/home-assistant/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/home-assistant/home-assistant/teams","hooks_url":"https://api.github.com/repos/home-assistant/home-assistant/hooks","issue_events_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/events{/number}","events_url":"https://api.github.com/repos/home-assistant/home-assistant/events","assignees_url":"https://api.github.com/repos/home-assistant/home-assistant/assignees{/user}","branches_url":"https://api.github.com/repos/home-assistant/home-assistant/branches{/branch}","tags_url":"https://api.github.com/repos/home-assistant/home-assistant/tags","blobs_url":"https://api.github.com/repos/home-assistant/home-assistant/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/home-assistant/home-assistant/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/home-assistant/home-assistant/git/refs{/sha}","trees_url":"https://api.github.com/repos/home-assistant/home-assistant/git/trees{/sha}","statuses_url":"https://api.github.com/repos/home-assistant/home-assistant/statuses/{sha}","languages_url":"https://api.github.com/repos/home-assistant/home-assistant/languages","stargazers_url":"https://api.github.com/repos/home-assistant/home-assistant/stargazers","contributors_url":"https://api.github.com/repos/home-assistant/home-assistant/contributors","subscribers_url":"https://api.github.com/repos/home-assistant/home-assistant/subscribers","subscription_url":"https://api.github.com/repos/home-assistant/home-assistant/subscription","commits_url":"https://api.github.com/repos/home-assistant/home-assistant/commits{/sha}","git_commits_url":"https://api.github.com/repos/home-assistant/home-assistant/git/commits{/sha}","comments_url":"https://api.github.com/repos/home-assistant/home-assistant/comments{/number}","issue_comment_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/comments{/number}","contents_url":"https://api.github.com/repos/home-assistant/home-assistant/contents/{+path}","compare_url":"https://api.github.com/repos/home-assistant/home-assistant/compare/{base}...{head}","merges_url":"https://api.github.com/repos/home-assistant/home-assistant/merges","archive_url":"https://api.github.com/repos/home-assistant/home-assistant/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/home-assistant/home-assistant/downloads","issues_url":"https://api.github.com/repos/home-assistant/home-assistant/issues{/number}","pulls_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls{/number}","milestones_url":"https://api.github.com/repos/home-assistant/home-assistant/milestones{/number}","notifications_url":"https://api.github.com/repos/home-assistant/home-assistant/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/home-assistant/home-assistant/labels{/name}","releases_url":"https://api.github.com/repos/home-assistant/home-assistant/releases{/id}","deployments_url":"https://api.github.com/repos/home-assistant/home-assistant/deployments","created_at":"2013-09-17T07:29:48Z","updated_at":"2016-10-19T06:51:15Z","pushed_at":"2016-10-19T08:12:36Z","git_url":"git://github.com/home-assistant/home-assistant.git","ssh_url":"git@github.com:home-assistant/home-assistant.git","clone_url":"https://github.com/home-assistant/home-assistant.git","svn_url":"https://github.com/home-assistant/home-assistant","homepage":"https://home-assistant.io","size":83305,"stargazers_count":4365,"watchers_count":4365,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1116,"mirror_url":null,"open_issues_count":281,"forks":1116,"open_issues":281,"watchers":4365,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946"},"html":{"href":"https://github.com/home-assistant/home-assistant/pull/3946"},"issue":{"href":"https://api.github.com/repos/home-assistant/home-assistant/issues/3946"},"comments":{"href":"https://api.github.com/repos/home-assistant/home-assistant/issues/3946/comments"},"review_comments":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946/comments"},"review_comment":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/3946/commits"},"statuses":{"href":"https://api.github.com/repos/home-assistant/home-assistant/statuses/b849d04b1d11fdf190fa41017a204a2c195d72dc"}}}}
{"actor":{"display_login":"balloobbot"}}