HA iOS support (#3752)
* Initial commit of the iOS component and platform * Allow extra * Add battery to identify, a new function to get devices, and load the upcoming sensor * Add iOS sensor platform, currently for battery state & level * Add discoverability for the iOS app * Convert single quote to double quotes * Load all required components and platforms when loading the iOS component for the best experience * Unify quote style to double * Change to hass_ios * Update push URL, add support for logging based on status code, log rate limit updates * Block iOS from coverage checks for now...pull/3987/head
parent
6e5a3c0a94
commit
ea91d24eb2
|
@ -31,6 +31,9 @@ omit =
|
|||
homeassistant/components/insteon_hub.py
|
||||
homeassistant/components/*/insteon_hub.py
|
||||
|
||||
homeassistant/components/ios.py
|
||||
homeassistant/components/*/ios.py
|
||||
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/*/isy994.py
|
||||
|
||||
|
|
|
@ -14,15 +14,17 @@ import voluptuous as vol
|
|||
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.helpers.discovery import load_platform, discover
|
||||
|
||||
REQUIREMENTS = ['netdisco==0.7.1']
|
||||
REQUIREMENTS = ['netdisco==0.7.2']
|
||||
|
||||
DOMAIN = 'discovery'
|
||||
|
||||
SCAN_INTERVAL = 300 # seconds
|
||||
SERVICE_NETGEAR = 'netgear_router'
|
||||
SERVICE_WEMO = 'belkin_wemo'
|
||||
SERVICE_HASS_IOS_APP = 'hass_ios'
|
||||
|
||||
SERVICE_HANDLERS = {
|
||||
SERVICE_HASS_IOS_APP: ('ios', None),
|
||||
SERVICE_NETGEAR: ('device_tracker', None),
|
||||
SERVICE_WEMO: ('wemo', None),
|
||||
'philips_hue': ('light', 'hue'),
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
"""
|
||||
Native Home Assistant iOS app component.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/ios/
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
import homeassistant.loader as loader
|
||||
|
||||
from homeassistant.helpers import discovery
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
|
||||
from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR,
|
||||
HTTP_BAD_REQUEST)
|
||||
|
||||
from homeassistant.components.notify import DOMAIN as NotifyDomain
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "ios"
|
||||
|
||||
DEPENDENCIES = ["http"]
|
||||
|
||||
CONF_PUSH = "push"
|
||||
CONF_PUSH_CATEGORIES = "categories"
|
||||
CONF_PUSH_CATEGORIES_NAME = "name"
|
||||
CONF_PUSH_CATEGORIES_IDENTIFIER = "identifier"
|
||||
CONF_PUSH_CATEGORIES_ACTIONS = "actions"
|
||||
|
||||
CONF_PUSH_ACTIONS_IDENTIFIER = "identifier"
|
||||
CONF_PUSH_ACTIONS_TITLE = "title"
|
||||
CONF_PUSH_ACTIONS_ACTIVATION_MODE = "activationMode"
|
||||
CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED = "authenticationRequired"
|
||||
CONF_PUSH_ACTIONS_DESTRUCTIVE = "destructive"
|
||||
CONF_PUSH_ACTIONS_BEHAVIOR = "behavior"
|
||||
CONF_PUSH_ACTIONS_CONTEXT = "context"
|
||||
CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE = "textInputButtonTitle"
|
||||
CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER = "textInputPlaceholder"
|
||||
|
||||
ATTR_FOREGROUND = "foreground"
|
||||
ATTR_BACKGROUND = "background"
|
||||
|
||||
ACTIVATION_MODES = [ATTR_FOREGROUND, ATTR_BACKGROUND]
|
||||
|
||||
ATTR_DEFAULT_BEHAVIOR = "default"
|
||||
ATTR_TEXT_INPUT_BEHAVIOR = "textInput"
|
||||
|
||||
BEHAVIORS = [ATTR_DEFAULT_BEHAVIOR, ATTR_TEXT_INPUT_BEHAVIOR]
|
||||
|
||||
ATTR_DEVICE = "device"
|
||||
ATTR_PUSH_TOKEN = "pushToken"
|
||||
ATTR_APP = "app"
|
||||
ATTR_PERMISSIONS = "permissions"
|
||||
ATTR_PUSH_ID = "pushId"
|
||||
ATTR_DEVICE_ID = "deviceId"
|
||||
ATTR_PUSH_SOUNDS = "pushSounds"
|
||||
ATTR_BATTERY = "battery"
|
||||
|
||||
ATTR_DEVICE_NAME = "name"
|
||||
ATTR_DEVICE_LOCALIZED_MODEL = "localizedModel"
|
||||
ATTR_DEVICE_MODEL = "model"
|
||||
ATTR_DEVICE_PERMANENT_ID = "permanentID"
|
||||
ATTR_DEVICE_SYSTEM_VERSION = "systemVersion"
|
||||
ATTR_DEVICE_TYPE = "type"
|
||||
ATTR_DEVICE_SYSTEM_NAME = "systemName"
|
||||
|
||||
ATTR_APP_BUNDLE_IDENTIFER = "bundleIdentifer"
|
||||
ATTR_APP_BUILD_NUMBER = "buildNumber"
|
||||
ATTR_APP_VERSION_NUMBER = "versionNumber"
|
||||
|
||||
ATTR_LOCATION_PERMISSION = "location"
|
||||
ATTR_NOTIFICATIONS_PERMISSION = "notifications"
|
||||
|
||||
PERMISSIONS = [ATTR_LOCATION_PERMISSION, ATTR_NOTIFICATIONS_PERMISSION]
|
||||
|
||||
ATTR_BATTERY_STATE = "state"
|
||||
ATTR_BATTERY_LEVEL = "level"
|
||||
|
||||
ATTR_BATTERY_STATE_UNPLUGGED = "Unplugged"
|
||||
ATTR_BATTERY_STATE_CHARGING = "Charging"
|
||||
ATTR_BATTERY_STATE_FULL = "Full"
|
||||
ATTR_BATTERY_STATE_UNKNOWN = "Unknown"
|
||||
|
||||
BATTERY_STATES = [ATTR_BATTERY_STATE_UNPLUGGED, ATTR_BATTERY_STATE_CHARGING,
|
||||
ATTR_BATTERY_STATE_FULL, ATTR_BATTERY_STATE_UNKNOWN]
|
||||
|
||||
ATTR_DEVICES = "devices"
|
||||
|
||||
ACTION_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PUSH_ACTIONS_IDENTIFIER): vol.Upper,
|
||||
vol.Required(CONF_PUSH_ACTIONS_TITLE): cv.string,
|
||||
vol.Optional(CONF_PUSH_ACTIONS_ACTIVATION_MODE,
|
||||
default=ATTR_BACKGROUND): vol.In(ACTIVATION_MODES),
|
||||
vol.Optional(CONF_PUSH_ACTIONS_AUTHENTICATION_REQUIRED,
|
||||
default=False): cv.boolean,
|
||||
vol.Optional(CONF_PUSH_ACTIONS_DESTRUCTIVE,
|
||||
default=False): cv.boolean,
|
||||
vol.Optional(CONF_PUSH_ACTIONS_BEHAVIOR,
|
||||
default=ATTR_DEFAULT_BEHAVIOR): vol.In(BEHAVIORS),
|
||||
vol.Optional(CONF_PUSH_ACTIONS_TEXT_INPUT_BUTTON_TITLE): cv.string,
|
||||
vol.Optional(CONF_PUSH_ACTIONS_TEXT_INPUT_PLACEHOLDER): cv.string,
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
ACTION_SCHEMA_LIST = vol.All(cv.ensure_list, [ACTION_SCHEMA])
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: {
|
||||
CONF_PUSH: {
|
||||
CONF_PUSH_CATEGORIES: vol.All(cv.ensure_list, [{
|
||||
vol.Required(CONF_PUSH_CATEGORIES_NAME): cv.string,
|
||||
vol.Required(CONF_PUSH_CATEGORIES_IDENTIFIER): vol.Upper,
|
||||
vol.Required(CONF_PUSH_CATEGORIES_ACTIONS): ACTION_SCHEMA_LIST
|
||||
}])
|
||||
}
|
||||
}
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
IDENTIFY_DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_DEVICE_NAME): cv.string,
|
||||
vol.Required(ATTR_DEVICE_LOCALIZED_MODEL): cv.string,
|
||||
vol.Required(ATTR_DEVICE_MODEL): cv.string,
|
||||
vol.Required(ATTR_DEVICE_PERMANENT_ID): cv.string,
|
||||
vol.Required(ATTR_DEVICE_SYSTEM_VERSION): cv.string,
|
||||
vol.Required(ATTR_DEVICE_TYPE): cv.string,
|
||||
vol.Required(ATTR_DEVICE_SYSTEM_NAME): cv.string,
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
IDENTIFY_DEVICE_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_DEVICE_SCHEMA)
|
||||
|
||||
IDENTIFY_APP_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_APP_BUNDLE_IDENTIFER): cv.string,
|
||||
vol.Required(ATTR_APP_BUILD_NUMBER): cv.positive_int,
|
||||
vol.Required(ATTR_APP_VERSION_NUMBER): cv.positive_int
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
IDENTIFY_APP_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_APP_SCHEMA)
|
||||
|
||||
IDENTIFY_BATTERY_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_BATTERY_LEVEL): cv.positive_int,
|
||||
vol.Required(ATTR_BATTERY_STATE): vol.In(BATTERY_STATES)
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
IDENTIFY_BATTERY_SCHEMA_CONTAINER = vol.All(dict, IDENTIFY_BATTERY_SCHEMA)
|
||||
|
||||
IDENTIFY_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_DEVICE): IDENTIFY_DEVICE_SCHEMA_CONTAINER,
|
||||
vol.Required(ATTR_BATTERY): IDENTIFY_BATTERY_SCHEMA_CONTAINER,
|
||||
vol.Required(ATTR_PUSH_TOKEN): cv.string,
|
||||
vol.Required(ATTR_APP): IDENTIFY_APP_SCHEMA_CONTAINER,
|
||||
vol.Required(ATTR_PERMISSIONS): vol.All(cv.ensure_list,
|
||||
[vol.In(PERMISSIONS)]),
|
||||
vol.Required(ATTR_PUSH_ID): cv.string,
|
||||
vol.Required(ATTR_DEVICE_ID): cv.string,
|
||||
vol.Optional(ATTR_PUSH_SOUNDS): list
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
CONFIGURATION_FILE = "ios.conf"
|
||||
|
||||
CONFIG_FILE = {ATTR_DEVICES: {}}
|
||||
|
||||
CONFIG_FILE_PATH = ""
|
||||
|
||||
|
||||
def _load_config(filename):
|
||||
"""Load configuration."""
|
||||
if not os.path.isfile(filename):
|
||||
return {}
|
||||
|
||||
try:
|
||||
with open(filename, "r") as fdesc:
|
||||
inp = fdesc.read()
|
||||
|
||||
# In case empty file
|
||||
if not inp:
|
||||
return {}
|
||||
|
||||
return json.loads(inp)
|
||||
except (IOError, ValueError) as error:
|
||||
_LOGGER.error("Reading config file %s failed: %s", filename, error)
|
||||
return None
|
||||
|
||||
|
||||
def _save_config(filename, config):
|
||||
"""Save configuration."""
|
||||
try:
|
||||
with open(filename, "w") as fdesc:
|
||||
fdesc.write(json.dumps(config))
|
||||
except (IOError, TypeError) as error:
|
||||
_LOGGER.error("Saving config file failed: %s", error)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def devices_with_push():
|
||||
"""Return a dictionary of push enabled targets."""
|
||||
targets = {}
|
||||
for device_name, device in CONFIG_FILE[ATTR_DEVICES].items():
|
||||
if device.get(ATTR_PUSH_ID) is not None:
|
||||
targets[device_name] = device.get(ATTR_PUSH_ID)
|
||||
return targets
|
||||
|
||||
|
||||
def enabled_push_ids():
|
||||
"""Return a list of push enabled target push IDs."""
|
||||
push_ids = list()
|
||||
# pylint: disable=unused-variable
|
||||
for device_name, device in CONFIG_FILE[ATTR_DEVICES].items():
|
||||
if device.get(ATTR_PUSH_ID) is not None:
|
||||
push_ids.append(device.get(ATTR_PUSH_ID))
|
||||
return push_ids
|
||||
|
||||
|
||||
def devices():
|
||||
"""Return a dictionary of all identified devices."""
|
||||
return CONFIG_FILE[ATTR_DEVICES]
|
||||
|
||||
|
||||
def device_name_for_push_id(push_id):
|
||||
"""Return the device name for the push ID."""
|
||||
for device_name, device in CONFIG_FILE[ATTR_DEVICES].items():
|
||||
if device.get(ATTR_PUSH_ID) is push_id:
|
||||
return device_name
|
||||
return None
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the iOS component."""
|
||||
# pylint: disable=global-statement, import-error
|
||||
global CONFIG_FILE
|
||||
global CONFIG_FILE_PATH
|
||||
|
||||
CONFIG_FILE_PATH = hass.config.path(CONFIGURATION_FILE)
|
||||
|
||||
CONFIG_FILE = _load_config(CONFIG_FILE_PATH)
|
||||
|
||||
if CONFIG_FILE == {}:
|
||||
CONFIG_FILE[ATTR_DEVICES] = {}
|
||||
|
||||
device_tracker = loader.get_component("device_tracker")
|
||||
if device_tracker.DOMAIN not in hass.config.components:
|
||||
device_tracker.setup(hass, {})
|
||||
# Need this to enable requirements checking in the app.
|
||||
hass.config.components.append(device_tracker.DOMAIN)
|
||||
|
||||
if "notify.ios" not in hass.config.components:
|
||||
notify = loader.get_component("notify.ios")
|
||||
notify.get_service(hass, {})
|
||||
# Need this to enable requirements checking in the app.
|
||||
if NotifyDomain not in hass.config.components:
|
||||
hass.config.components.append(NotifyDomain)
|
||||
|
||||
zeroconf = loader.get_component("zeroconf")
|
||||
if zeroconf.DOMAIN not in hass.config.components:
|
||||
zeroconf.setup(hass, config)
|
||||
# Need this to enable requirements checking in the app.
|
||||
hass.config.components.append(zeroconf.DOMAIN)
|
||||
|
||||
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
|
||||
|
||||
hass.wsgi.register_view(iOSIdentifyDeviceView(hass))
|
||||
|
||||
if config.get(DOMAIN) is not None:
|
||||
app_config = config[DOMAIN]
|
||||
if app_config.get(CONF_PUSH) is not None:
|
||||
push_config = app_config[CONF_PUSH]
|
||||
hass.wsgi.register_view(iOSPushConfigView(hass, push_config))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
class iOSPushConfigView(HomeAssistantView):
|
||||
"""A view that provides the push categories configuration."""
|
||||
|
||||
url = "/api/ios/push"
|
||||
name = "api:ios:push"
|
||||
|
||||
def __init__(self, hass, push_config):
|
||||
"""Init the view."""
|
||||
super().__init__(hass)
|
||||
self.push_config = push_config
|
||||
|
||||
def get(self, request):
|
||||
"""Handle the GET request for the push configuration."""
|
||||
return self.json(self.push_config)
|
||||
|
||||
|
||||
class iOSIdentifyDeviceView(HomeAssistantView):
|
||||
"""A view that accepts device identification requests."""
|
||||
|
||||
url = "/api/ios/identify"
|
||||
name = "api:ios:identify"
|
||||
|
||||
def __init__(self, hass):
|
||||
"""Init the view."""
|
||||
super().__init__(hass)
|
||||
|
||||
def post(self, request):
|
||||
"""Handle the POST request for device identification."""
|
||||
try:
|
||||
data = IDENTIFY_SCHEMA(request.json)
|
||||
except vol.Invalid as ex:
|
||||
return self.json_message(humanize_error(request.json, ex),
|
||||
HTTP_BAD_REQUEST)
|
||||
|
||||
name = data.get(ATTR_DEVICE_ID)
|
||||
|
||||
CONFIG_FILE[ATTR_DEVICES][name] = data
|
||||
|
||||
if not _save_config(CONFIG_FILE_PATH, CONFIG_FILE):
|
||||
return self.json_message("Error saving device.",
|
||||
HTTP_INTERNAL_SERVER_ERROR)
|
||||
|
||||
return self.json({"status": "registered"})
|
|
@ -0,0 +1,87 @@
|
|||
"""
|
||||
iOS push notification platform for notify component.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/notify.ios/
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
import requests
|
||||
|
||||
from homeassistant.components import ios
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_MESSAGE,
|
||||
ATTR_DATA, BaseNotificationService)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PUSH_URL = "https://ios-push.home-assistant.io/push"
|
||||
|
||||
DEPENDENCIES = ["ios"]
|
||||
|
||||
|
||||
def get_service(hass, config):
|
||||
"""Get the iOS notification service."""
|
||||
if "notify.ios" not in hass.config.components:
|
||||
# Need this to enable requirements checking in the app.
|
||||
hass.config.components.append("notify.ios")
|
||||
|
||||
return iOSNotificationService()
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods, too-many-arguments, invalid-name
|
||||
class iOSNotificationService(BaseNotificationService):
|
||||
"""Implement the notification service for iOS."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the service."""
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
"""Return a dictionary of registered targets."""
|
||||
return ios.devices_with_push()
|
||||
|
||||
def send_message(self, message="", **kwargs):
|
||||
"""Send a message to the Lambda APNS gateway."""
|
||||
data = {ATTR_MESSAGE: message}
|
||||
|
||||
if kwargs.get(ATTR_TITLE) is not None:
|
||||
# Remove default title from notifications.
|
||||
if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT:
|
||||
data[ATTR_TITLE] = kwargs.get(ATTR_TITLE)
|
||||
|
||||
targets = kwargs.get(ATTR_TARGET)
|
||||
|
||||
if not targets:
|
||||
targets = ios.enabled_push_ids()
|
||||
|
||||
if kwargs.get(ATTR_DATA) is not None:
|
||||
data[ATTR_DATA] = kwargs.get(ATTR_DATA)
|
||||
|
||||
for target in targets:
|
||||
data[ATTR_TARGET] = target
|
||||
|
||||
req = requests.post(PUSH_URL, json=data, timeout=10)
|
||||
|
||||
if req.status_code is not 201:
|
||||
message = req.json()["message"]
|
||||
if req.status_code is 429:
|
||||
_LOGGER.warning(message)
|
||||
elif req.status_code is 400 or 500:
|
||||
_LOGGER.error(message)
|
||||
|
||||
if req.status_code in (201, 429):
|
||||
rate_limits = req.json()["rateLimits"]
|
||||
resetsAt = dt_util.parse_datetime(rate_limits["resetsAt"])
|
||||
resetsAtTime = resetsAt - datetime.now(timezone.utc)
|
||||
rate_limit_msg = ("iOS push notification rate limits for %s: "
|
||||
"%d sent, %d allowed, %d errors, "
|
||||
"resets in %s")
|
||||
_LOGGER.info(rate_limit_msg,
|
||||
ios.device_name_for_push_id(target),
|
||||
rate_limits["successful"],
|
||||
rate_limits["maximum"], rate_limits["errors"],
|
||||
str(resetsAtTime).split(".")[0])
|
|
@ -0,0 +1,112 @@
|
|||
"""
|
||||
Support for Home Assistant iOS app sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.ios/
|
||||
"""
|
||||
from homeassistant.components import ios
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
DEPENDENCIES = ["ios"]
|
||||
|
||||
SENSOR_TYPES = {
|
||||
"level": ["Battery Level", "%"],
|
||||
"state": ["Battery State", None]
|
||||
}
|
||||
|
||||
DEFAULT_ICON = "mdi:battery"
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the iOS sensor."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
dev = list()
|
||||
for device_name, device in ios.devices().items():
|
||||
for sensor_type in ("level", "state"):
|
||||
dev.append(IOSSensor(sensor_type, device_name, device))
|
||||
|
||||
add_devices(dev)
|
||||
|
||||
|
||||
class IOSSensor(Entity):
|
||||
"""Representation of an iOS sensor."""
|
||||
|
||||
def __init__(self, sensor_type, device_name, device):
|
||||
"""Initialize the sensor."""
|
||||
self._device_name = device_name
|
||||
self._name = device_name + " " + SENSOR_TYPES[sensor_type][0]
|
||||
self._device = device
|
||||
self.type = sensor_type
|
||||
self._state = None
|
||||
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the iOS sensor."""
|
||||
device_name = self._device[ios.ATTR_DEVICE][ios.ATTR_DEVICE_NAME]
|
||||
return "{} {}".format(device_name, SENSOR_TYPES[self.type][0])
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID of this sensor."""
|
||||
return "sensor_ios_battery_{}_{}".format(self.type, self._device_name)
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement this sensor expresses itself in."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
device = self._device[ios.ATTR_DEVICE]
|
||||
device_battery = self._device[ios.ATTR_BATTERY]
|
||||
return {
|
||||
"Battery State": device_battery[ios.ATTR_BATTERY_STATE],
|
||||
"Battery Level": device_battery[ios.ATTR_BATTERY_LEVEL],
|
||||
"Device Type": device[ios.ATTR_DEVICE_TYPE],
|
||||
"Device Name": device[ios.ATTR_DEVICE_NAME],
|
||||
"Device Version": device[ios.ATTR_DEVICE_SYSTEM_VERSION],
|
||||
}
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
device_battery = self._device[ios.ATTR_BATTERY]
|
||||
battery_state = device_battery[ios.ATTR_BATTERY_STATE]
|
||||
battery_level = device_battery[ios.ATTR_BATTERY_LEVEL]
|
||||
rounded_level = round(battery_level, -1)
|
||||
returning_icon = DEFAULT_ICON
|
||||
if battery_state == ios.ATTR_BATTERY_STATE_FULL:
|
||||
returning_icon = DEFAULT_ICON
|
||||
elif battery_state == ios.ATTR_BATTERY_STATE_CHARGING:
|
||||
# Why is MDI missing 10, 50, 70?
|
||||
if rounded_level in (20, 30, 40, 60, 80, 90, 100):
|
||||
returning_icon = "{}-charging-{}".format(DEFAULT_ICON,
|
||||
str(rounded_level))
|
||||
else:
|
||||
returning_icon = "{}-charging".format(DEFAULT_ICON)
|
||||
elif battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED:
|
||||
if rounded_level < 10:
|
||||
returning_icon = "{}-outline".format(DEFAULT_ICON)
|
||||
elif battery_level == 100:
|
||||
returning_icon = DEFAULT_ICON
|
||||
else:
|
||||
returning_icon = "{}-{}".format(DEFAULT_ICON,
|
||||
str(rounded_level))
|
||||
elif battery_state == ios.ATTR_BATTERY_STATE_UNKNOWN:
|
||||
returning_icon = "{}-unknown".format(DEFAULT_ICON)
|
||||
|
||||
return returning_icon
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
self._device = ios.devices().get(self._device_name)
|
||||
self._state = self._device[ios.ATTR_BATTERY][self.type]
|
|
@ -277,7 +277,7 @@ mficlient==0.3.0
|
|||
miflora==0.1.9
|
||||
|
||||
# homeassistant.components.discovery
|
||||
netdisco==0.7.1
|
||||
netdisco==0.7.2
|
||||
|
||||
# homeassistant.components.sensor.neurio_energy
|
||||
neurio==0.2.10
|
||||
|
|
Loading…
Reference in New Issue