From 820780996a30768c33d94d0218892ac27d222f10 Mon Sep 17 00:00:00 2001
From: Quentame <polletquentin74@me.com>
Date: Sat, 14 Dec 2019 23:06:00 +0100
Subject: [PATCH] Add battery sensor to iCloud (#29818)

* Add battery sensor to iCloud

* Update .coveragerc

* Review: @balloob & @MartinHjelmare

* Review: use f string
---
 .coveragerc                                   |  1 +
 homeassistant/components/icloud/__init__.py   |  5 --
 homeassistant/components/icloud/const.py      |  3 +-
 .../components/icloud/device_tracker.py       | 30 ++++---
 homeassistant/components/icloud/sensor.py     | 85 +++++++++++++++++++
 5 files changed, 104 insertions(+), 20 deletions(-)
 create mode 100644 homeassistant/components/icloud/sensor.py

diff --git a/.coveragerc b/.coveragerc
index f4794b59381..c6e7182d326 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -321,6 +321,7 @@ omit =
     homeassistant/components/iaqualink/switch.py
     homeassistant/components/icloud/__init__.py
     homeassistant/components/icloud/device_tracker.py
+    homeassistant/components/icloud/sensor.py
     homeassistant/components/izone/climate.py
     homeassistant/components/izone/discovery.py
     homeassistant/components/izone/__init__.py
diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py
index 2012f691938..c59f4098951 100644
--- a/homeassistant/components/icloud/__init__.py
+++ b/homeassistant/components/icloud/__init__.py
@@ -560,11 +560,6 @@ class IcloudDevice:
         """Return a unique ID."""
         return self._device_id
 
-    @property
-    def dev_id(self) -> str:
-        """Return the device ID."""
-        return self._device_id
-
     @property
     def name(self) -> str:
         """Return the Apple device name."""
diff --git a/homeassistant/components/icloud/const.py b/homeassistant/components/icloud/const.py
index 4e99a378077..ed2fc78fe6d 100644
--- a/homeassistant/components/icloud/const.py
+++ b/homeassistant/components/icloud/const.py
@@ -14,8 +14,7 @@ DEFAULT_GPS_ACCURACY_THRESHOLD = 500  # meters
 STORAGE_KEY = DOMAIN
 STORAGE_VERSION = 1
 
-# Next PR will add sensor
-ICLOUD_COMPONENTS = ["device_tracker"]
+ICLOUD_COMPONENTS = ["device_tracker", "sensor"]
 
 # pyicloud.AppleDevice status
 DEVICE_BATTERY_LEVEL = "batteryLevel"
diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py
index 4be34728c6d..511ce7f9447 100644
--- a/homeassistant/components/icloud/device_tracker.py
+++ b/homeassistant/components/icloud/device_tracker.py
@@ -1,8 +1,10 @@
 """Support for tracking for iCloud devices."""
 import logging
+from typing import Dict
 
 from homeassistant.components.device_tracker import SOURCE_TYPE_GPS
 from homeassistant.components.device_tracker.config_entry import TrackerEntity
+from homeassistant.config_entries import ConfigEntry
 from homeassistant.const import CONF_USERNAME
 from homeassistant.helpers.dispatcher import async_dispatcher_connect
 from homeassistant.helpers.typing import HomeAssistantType
@@ -26,13 +28,15 @@ async def async_setup_scanner(
     pass
 
 
-async def async_setup_entry(hass: HomeAssistantType, entry, async_add_entities):
+async def async_setup_entry(
+    hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
+):
     """Configure a dispatcher connection based on a config entry."""
     username = entry.data[CONF_USERNAME]
 
     for device in hass.data[DOMAIN][username].devices.values():
         if device.location is None:
-            _LOGGER.debug("No position found for device %s", device.name)
+            _LOGGER.debug("No position found for %s", device.name)
             continue
 
         _LOGGER.debug("Adding device_tracker for %s", device.name)
@@ -49,12 +53,12 @@ class IcloudTrackerEntity(TrackerEntity):
         self._unsub_dispatcher = None
 
     @property
-    def unique_id(self):
+    def unique_id(self) -> str:
         """Return a unique ID."""
-        return f"{self._device.unique_id}_tracker"
+        return self._device.unique_id
 
     @property
-    def name(self):
+    def name(self) -> str:
         """Return the name of the device."""
         return self._device.name
 
@@ -74,36 +78,36 @@ class IcloudTrackerEntity(TrackerEntity):
         return self._device.location[DEVICE_LOCATION_LONGITUDE]
 
     @property
-    def should_poll(self):
+    def should_poll(self) -> bool:
         """No polling needed."""
         return False
 
     @property
-    def battery_level(self):
+    def battery_level(self) -> int:
         """Return the battery level of the device."""
         return self._device.battery_level
 
     @property
-    def source_type(self):
+    def source_type(self) -> str:
         """Return the source type, eg gps or router, of the device."""
         return SOURCE_TYPE_GPS
 
     @property
-    def icon(self):
+    def icon(self) -> str:
         """Return the icon."""
         return icon_for_icloud_device(self._device)
 
     @property
-    def device_state_attributes(self):
+    def device_state_attributes(self) -> Dict[str, any]:
         """Return the device state attributes."""
         return self._device.state_attributes
 
     @property
-    def device_info(self):
+    def device_info(self) -> Dict[str, any]:
         """Return the device information."""
         return {
-            "identifiers": {(DOMAIN, self.unique_id)},
-            "name": self.name,
+            "identifiers": {(DOMAIN, self._device.unique_id)},
+            "name": self._device.name,
             "manufacturer": "Apple",
             "model": self._device.device_model,
         }
diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py
new file mode 100644
index 00000000000..4351d4ffa19
--- /dev/null
+++ b/homeassistant/components/icloud/sensor.py
@@ -0,0 +1,85 @@
+"""Support for iCloud sensors."""
+import logging
+from typing import Dict
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_USERNAME, DEVICE_CLASS_BATTERY
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.icon import icon_for_battery_level
+from homeassistant.helpers.typing import HomeAssistantType
+
+from . import IcloudDevice
+from .const import DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(
+    hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
+) -> None:
+    """Set up iCloud devices sensors based on a config entry."""
+    username = entry.data[CONF_USERNAME]
+
+    entities = []
+    for device in hass.data[DOMAIN][username].devices.values():
+        if device.battery_level is not None:
+            _LOGGER.debug("Adding battery sensor for %s", device.name)
+            entities.append(IcloudDeviceBatterySensor(device))
+
+    async_add_entities(entities, True)
+
+
+class IcloudDeviceBatterySensor(Entity):
+    """Representation of a iCloud device battery sensor."""
+
+    def __init__(self, device: IcloudDevice):
+        """Initialize the battery sensor."""
+        self._device = device
+
+    @property
+    def unique_id(self) -> str:
+        """Return a unique ID."""
+        return f"{self._device.unique_id}_battery"
+
+    @property
+    def name(self) -> str:
+        """Sensor name."""
+        return f"{self._device.name} battery state"
+
+    @property
+    def device_class(self) -> str:
+        """Return the device class of the sensor."""
+        return DEVICE_CLASS_BATTERY
+
+    @property
+    def state(self) -> int:
+        """Battery state percentage."""
+        return self._device.battery_level
+
+    @property
+    def unit_of_measurement(self) -> str:
+        """Battery state measured in percentage."""
+        return "%"
+
+    @property
+    def icon(self) -> str:
+        """Battery state icon handling."""
+        return icon_for_battery_level(
+            battery_level=self._device.battery_level,
+            charging=self._device.battery_status == "Charging",
+        )
+
+    @property
+    def device_state_attributes(self) -> Dict[str, any]:
+        """Return default attributes for the iCloud device entity."""
+        return self._device.state_attributes
+
+    @property
+    def device_info(self) -> Dict[str, any]:
+        """Return the device information."""
+        return {
+            "identifiers": {(DOMAIN, self._device.unique_id)},
+            "name": self._device.name,
+            "manufacturer": "Apple",
+            "model": self._device.device_model,
+        }
-- 
GitLab